feat: add Master Admin application functionality

This commit is contained in:
Gagan7900
2026-07-24 11:24:55 +05:30
parent 4cb9b0181e
commit a29cb34669
251 changed files with 37133 additions and 461 deletions
@@ -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;
}
}