feat: add Master Admin application functionality
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { guestGuard } from '../../core/guards/auth/guest.guard';
|
||||
|
||||
export const authen: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
canActivate: [guestGuard],
|
||||
loadComponent: () => import('./pages/login/login').then((m) => m.Login),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('../errors/error.routes').then((m) => m.errorRoutingModule),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,85 @@
|
||||
<div
|
||||
class="grid grid-cols-12 h-screen min-h-screen overflow-hidden bg-bodybg dark:bg-bodybg text-defaulttextcolor text-defaultsize">
|
||||
<div class="hidden xl:block xl:col-span-6 h-screen"> <img src="./assets/images/authentication/auth-cover.png"
|
||||
class="w-full h-full object-cover object-left" alt="login cover" /> </div>
|
||||
<div class="xl:col-span-6 col-span-12 min-h-[100dvh] flex items-center justify-center px-6 bg-bodybg dark:bg-bodybg">
|
||||
<div class="authentication authentication-basic w-full max-w-[420px]">
|
||||
|
||||
<div class="my-[2.5rem] flex justify-center">
|
||||
<img src="./assets/images/brand-logos/erp-logo-icon.png" alt="logo" class="desktop-logo">
|
||||
<img src="./assets/images/brand-logos/erp-logo-icon.png" alt="logo" class="desktop-dark">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="box !mb-0">
|
||||
<div class="box-body !p-[3rem]">
|
||||
<p class="h5 font-semibold mb-2 text-center">Sign In</p>
|
||||
<p class="mb-4 text-[#8c9097] dark:text-white/50 opacity-[0.7] font-normal text-center">
|
||||
Welcome back !
|
||||
</p>
|
||||
|
||||
<form [formGroup]="adminLoginForm" (ngSubmit)="login()">
|
||||
@if (loginError) {
|
||||
<div class="text-danger mb-3">{{ loginError }}</div>
|
||||
}
|
||||
|
||||
<div class="grid grid-cols-12 gap-y-4">
|
||||
<div class="xl:col-span-12 col-span-12">
|
||||
<label for="signin-username" class="form-label text-default">User Name</label>
|
||||
<input type="text" class="form-control form-control-lg w-full !rounded-md"
|
||||
id="signin-username" placeholder="user name" formControlName="username"
|
||||
autocomplete="username">
|
||||
</div>
|
||||
|
||||
<div class="xl:col-span-12 col-span-12 mb-2">
|
||||
<label for="signin-password" class="form-label text-default block">Password</label>
|
||||
|
||||
<a routerLink="/authentication/reset-password/basic"
|
||||
class="ltr:float-right rtl:float-left text-danger">
|
||||
Forgot password ?
|
||||
</a>
|
||||
|
||||
<div class="input-group">
|
||||
<input class="form-control form-control-lg !rounded-s-md" id="signin-password"
|
||||
placeholder="password" formControlName="password"
|
||||
autocomplete="current-password"
|
||||
[type]="visibilityMap['Angular'] ? 'text' : 'password'">
|
||||
|
||||
<button aria-label="button" class="ti-btn ti-btn-light !rounded-s-none !mb-0"
|
||||
type="button" (click)="toggleVisibility('Angular')" id="button-addon2">
|
||||
<i class="{{ iconMap['Angular'] }} align-middle"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<div class="form-check !ps-0">
|
||||
<input class="form-check-input" type="checkbox" id="defaultCheck1"
|
||||
formControlName="rememberMe">
|
||||
|
||||
<label class="form-check-label text-[#8c9097] dark:text-white/50 font-normal"
|
||||
for="defaultCheck1">
|
||||
Remember password ?
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:col-span-12 col-span-12 grid mt-2">
|
||||
<button type="submit" [disabled]="adminLoginForm.invalid || isSubmitting"
|
||||
class="ti-btn ti-btn-primary !bg-primary !text-white !font-medium inline-flex items-center justify-center gap-2">
|
||||
|
||||
<span [style.display]="isSubmitting ? '' : 'none'" class="ti-spinner text-white"
|
||||
role="status" aria-label="loading"></span>
|
||||
<span [style.display]="isSubmitting ? '' : 'none'">Signing In...</span>
|
||||
<span [style.display]="isSubmitting ? 'none' : ''">Sign In</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
::ng-deep{
|
||||
|
||||
|
||||
.firebase {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.rounded-lg{
|
||||
width: 120px;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user