123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
import { ChangeDetectorRef, Component, inject } from '@angular/core';
|
|
import { FormBuilder, Validators } from '@angular/forms';
|
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
|
import { AuthService } from '../../../../core/services/auth/auth.service';
|
|
import { ReactiveFormsModule } from '@angular/forms';
|
|
import { ToastrService } from 'ngx-toastr';
|
|
import { catchError, finalize, of, switchMap, tap } from 'rxjs';
|
|
import { AppContextService } from '../../../../core/services/context/app-context.service';
|
|
|
|
@Component({
|
|
selector: 'app-login',
|
|
standalone: true,
|
|
imports: [RouterModule, ReactiveFormsModule],
|
|
|
|
templateUrl: './login.html',
|
|
styleUrl: './login.scss'
|
|
})
|
|
export class Login {
|
|
private readonly formBuilder = inject(FormBuilder);
|
|
private readonly cdr = inject(ChangeDetectorRef);
|
|
private readonly fallbackRoute = '/dashboards/crm';
|
|
|
|
public readonly adminLoginForm = this.formBuilder.nonNullable.group({
|
|
username: ['', [Validators.required, Validators.email]],
|
|
password: ['', Validators.required],
|
|
rememberMe: [false]
|
|
});
|
|
public isSubmitting = false;
|
|
public loginError = '';
|
|
public visibilityMap: Record<string, boolean> = {
|
|
Angular: false
|
|
};
|
|
public iconMap: Record<string, string> = {
|
|
Angular: 'fe fe-eye-off'
|
|
};
|
|
|
|
constructor(
|
|
public authservice: AuthService,
|
|
private appContextService: AppContextService,
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private toastr: ToastrService
|
|
) { }
|
|
|
|
login() {
|
|
this.loginError = '';
|
|
|
|
if (this.adminLoginForm.invalid) {
|
|
this.adminLoginForm.markAllAsTouched();
|
|
return;
|
|
}
|
|
|
|
if (this.isSubmitting) {
|
|
return;
|
|
}
|
|
|
|
this.isSubmitting = true;
|
|
|
|
const { username, password, rememberMe } = this.adminLoginForm.getRawValue();
|
|
|
|
this.authservice.login({ email: username, password }, !!rememberMe)
|
|
.pipe(
|
|
switchMap(() =>
|
|
this.appContextService.ensureMenuInitialized(true).pipe(
|
|
catchError(() => {
|
|
this.authservice.logout();
|
|
this.loginError = 'Unable to load application context.';
|
|
return of(null);
|
|
})
|
|
)
|
|
),
|
|
finalize(() => {
|
|
this.isSubmitting = false;
|
|
this.cdr.detectChanges();
|
|
})
|
|
)
|
|
.subscribe({
|
|
next: () => {
|
|
if (this.loginError) {
|
|
this.toastr.error(this.loginError);
|
|
return;
|
|
}
|
|
|
|
void this.router.navigateByUrl(this.getSafeReturnUrl());
|
|
this.toastr.success('Login successful', username);
|
|
},
|
|
error: (error) => {
|
|
this.loginError =
|
|
error?.error?.detail ||
|
|
error?.error?.message ||
|
|
'Invalid email or password';
|
|
|
|
this.toastr.error(this.loginError);
|
|
}
|
|
});
|
|
}
|
|
|
|
toggleVisibility(tab: string): void {
|
|
this.visibilityMap[tab] = !this.visibilityMap[tab];
|
|
this.iconMap[tab] = this.visibilityMap[tab] ? 'fe fe-eye' : 'fe fe-eye-off';
|
|
}
|
|
|
|
private getSafeReturnUrl(): string {
|
|
const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl')?.trim() ?? '';
|
|
if (!returnUrl) {
|
|
return this.fallbackRoute;
|
|
}
|
|
|
|
const lowerReturnUrl = returnUrl.toLowerCase();
|
|
const isSafeInternalUrl =
|
|
returnUrl.startsWith('/') &&
|
|
!returnUrl.startsWith('//') &&
|
|
!returnUrl.includes('\\') &&
|
|
!lowerReturnUrl.includes('http://') &&
|
|
!lowerReturnUrl.includes('https://');
|
|
|
|
return isSafeInternalUrl ? returnUrl : this.fallbackRoute;
|
|
}
|
|
}
|
|
|
|
|
|
|