Compare commits
No commits in common. "a1f31faf735e994c32a902450e1e7b710f945c83" and "e2265185875cf561dc0bac7da3470bb0688524a4" have entirely different histories.
a1f31faf73
...
e226518587
@ -123,6 +123,9 @@
|
|||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"deploy": {
|
||||||
|
"builder": "angular-cli-ghpages:deploy"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"i18n": {
|
"i18n": {
|
||||||
|
|||||||
1013
package-lock.json
generated
1013
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,6 @@
|
|||||||
"ngx-cookie-service": "^20.0.1",
|
"ngx-cookie-service": "^20.0.1",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"xlsx": "^0.18.5",
|
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -38,6 +37,7 @@
|
|||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jasmine": "~5.1.0",
|
"@types/jasmine": "~5.1.0",
|
||||||
"@types/node": "^18.18.0",
|
"@types/node": "^18.18.0",
|
||||||
|
"angular-cli-ghpages": "^2.0.3",
|
||||||
"jasmine-core": "~5.6.0",
|
"jasmine-core": "~5.6.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
|||||||
@ -6,13 +6,9 @@ import { AddServiceProviderComponent } from './service-provider/add/add-service-
|
|||||||
import { EditServiceProviderComponent } from './service-provider/edit/edit-service-provider.component';
|
import { EditServiceProviderComponent } from './service-provider/edit/edit-service-provider.component';
|
||||||
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
|
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
|
||||||
import { UserSettingsComponent } from './user-settings/user-settings.component';
|
import { UserSettingsComponent } from './user-settings/user-settings.component';
|
||||||
import { RegisterComponent } from './register/register.component';
|
|
||||||
import { ForgotPasswordComponent } from './forgot-password/forgot-password.component';
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent },
|
||||||
{ path: 'register', component: RegisterComponent },
|
|
||||||
{ path: 'forgot-password', component: ForgotPasswordComponent },
|
|
||||||
{ path: 'usersettings', component: UserSettingsComponent, canActivate: [AuthGuard] },
|
{ path: 'usersettings', component: UserSettingsComponent, canActivate: [AuthGuard] },
|
||||||
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard] },
|
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard] },
|
||||||
{ path: 'service-provider/:id', component: EditServiceProviderComponent, canActivate: [AuthGuard] },
|
{ path: 'service-provider/:id', component: EditServiceProviderComponent, canActivate: [AuthGuard] },
|
||||||
|
|||||||
21
src/app/auth/auth.guard.ts
Normal file
21
src/app/auth/auth.guard.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
|
||||||
|
import { UserService } from '../core/services/common/user.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
constructor(private userService: UserService, private router: Router) { }
|
||||||
|
|
||||||
|
canActivate(
|
||||||
|
next: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot): boolean {
|
||||||
|
if (this.userService.isLoggedIn()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,6 @@ import { AuthService } from '../services/common/auth.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthInterceptor implements HttpInterceptor {
|
export class AuthInterceptor implements HttpInterceptor {
|
||||||
private excludedUrls = ['/register', '/forgot-password'];
|
|
||||||
|
|
||||||
private isRefreshing = false;
|
private isRefreshing = false;
|
||||||
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||||
@ -18,11 +17,6 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
|
|
||||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
|
||||||
// Add withCredentials to all requests except excluded URLs
|
|
||||||
if (this.excludedUrls.some(url => request.url.includes(url))) {
|
|
||||||
return next.handle(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add withCredentials to all requests
|
// Add withCredentials to all requests
|
||||||
request = request.clone({
|
request = request.clone({
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
|
|||||||
@ -13,7 +13,6 @@ export interface Menu {
|
|||||||
export interface UserDetail {
|
export interface UserDetail {
|
||||||
spid: number;
|
spid: number;
|
||||||
clientid: number;
|
clientid: number;
|
||||||
locationid: number;
|
|
||||||
urlKey: string;
|
urlKey: string;
|
||||||
logoName: string;
|
logoName: string;
|
||||||
themeName: string;
|
themeName: string;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
@ -45,33 +45,4 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
register(email: string, password: string): Observable<any> {
|
|
||||||
|
|
||||||
return this.http.post(
|
|
||||||
`${this.apiUrl}/register`,
|
|
||||||
{ P_EMAILADDR: email, P_PASSWORD: password }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
forgotPassword(email: string, password: string, token: string): Observable<any> {
|
|
||||||
let headers = new HttpHeaders();
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
headers = headers.set('Authorization', `Bearer ${token}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http.put(
|
|
||||||
`${this.apiUrl}/forgot-password`,
|
|
||||||
{ P_EMAILADDR: email, P_PASSWORD: password },
|
|
||||||
{ headers }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEmail(email: string, type: string): Observable<any> {
|
|
||||||
return this.http.post(
|
|
||||||
`${this.apiUrl}/sendmail`,
|
|
||||||
{ P_TO: email, P_MAIL_TYPE: type }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,9 @@ export class UserService {
|
|||||||
private apiUrl = environment.apiUrl;
|
private apiUrl = environment.apiUrl;
|
||||||
private apiDb = environment.apiDb;
|
private apiDb = environment.apiDb;
|
||||||
|
|
||||||
|
private spid: number = 0;
|
||||||
|
private clientid: number = 0;
|
||||||
|
|
||||||
userDetailsSignal = signal<User>({});
|
userDetailsSignal = signal<User>({});
|
||||||
|
|
||||||
private readonly USER_EMAIL_KEY = 'CurrentUserEmail';
|
private readonly USER_EMAIL_KEY = 'CurrentUserEmail';
|
||||||
@ -95,18 +98,23 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUserSpid(): number {
|
getUserSpid(): number {
|
||||||
const userDetails = this.getUserDetails();
|
if (this.spid === 0) {
|
||||||
return userDetails?.userDetails?.spid ?? 0;
|
const userDetails = this.getUserDetails();
|
||||||
|
if (userDetails && userDetails.userDetails && userDetails.userDetails.spid) {
|
||||||
|
this.spid = userDetails.userDetails.spid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.spid;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserClientid(): number {
|
getUserClientid(): number {
|
||||||
const userDetails = this.getUserDetails();
|
if (this.clientid === 0) {
|
||||||
return userDetails?.userDetails?.clientid ?? 0;
|
const userDetails = this.getUserDetails();
|
||||||
}
|
if (userDetails && userDetails.userDetails && userDetails.userDetails.clientid) {
|
||||||
|
this.clientid = userDetails.userDetails.clientid;
|
||||||
getUserLocationid(): number {
|
}
|
||||||
const userDetails = this.getUserDetails();
|
}
|
||||||
return userDetails?.userDetails?.locationid ?? 0;
|
return this.clientid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapToUser(data: any): User {
|
private mapToUser(data: any): User {
|
||||||
@ -133,7 +141,6 @@ export class UserService {
|
|||||||
return {
|
return {
|
||||||
spid: userDetails.SPID,
|
spid: userDetails.SPID,
|
||||||
clientid: userDetails.CLIENTID,
|
clientid: userDetails.CLIENTID,
|
||||||
locationid: userDetails.LOCATIONID,
|
|
||||||
urlKey: userDetails.ENCURLKEY,
|
urlKey: userDetails.ENCURLKEY,
|
||||||
logoName: userDetails.LOGONAME,
|
logoName: userDetails.LOGONAME,
|
||||||
themeName: userDetails.THEMENAME
|
themeName: userDetails.THEMENAME
|
||||||
|
|||||||
@ -1,105 +0,0 @@
|
|||||||
<div class="forgot-password-container">
|
|
||||||
<div class="forgot-password-card">
|
|
||||||
<h3 class="subtitle">
|
|
||||||
{{forgotPasswordRequestCompleted ? 'Reset Password' : 'Forgot Password'}}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<form *ngIf="!forgotPasswordRequestCompleted" [formGroup]="forgotPasswordRequestForm" (ngSubmit)="onRequest()"
|
|
||||||
class="forgot-password-form">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Email</mat-label>
|
|
||||||
<input matInput formControlName="email" type="email" required>
|
|
||||||
<mat-icon matSuffix>email</mat-icon>
|
|
||||||
<mat-error *ngIf="email?.errors?.['required']">
|
|
||||||
Email is required
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="email?.errors?.['email']">
|
|
||||||
Please enter a valid email
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div class="login">
|
|
||||||
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button mat-raised-button color="primary" type="submit"
|
|
||||||
[disabled]="forgotPasswordRequestForm.invalid || isLoading">
|
|
||||||
<span *ngIf="!isLoading">Request</span>
|
|
||||||
<mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<form *ngIf="forgotPasswordRequestCompleted" [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()"
|
|
||||||
class="forgot-password-form">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Email</mat-label>
|
|
||||||
<input matInput formControlName="email" type="email" required>
|
|
||||||
<mat-icon matSuffix>email</mat-icon>
|
|
||||||
<mat-error *ngIf="email?.errors?.['required']">
|
|
||||||
Email is required
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="email?.errors?.['email']">
|
|
||||||
Please enter a valid email
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Password</mat-label>
|
|
||||||
<input matInput formControlName="password" [type]="hidePassword ? 'password' : 'text'" required>
|
|
||||||
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
|
||||||
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-error *ngIf="password?.errors?.['required']">
|
|
||||||
Password is required
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="password?.errors?.['minlength']">
|
|
||||||
Password must be at least 8 characters
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="password?.errors?.['pattern']">
|
|
||||||
Password must contain at least one uppercase letter, one lowercase letter, one number, and one
|
|
||||||
special character
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Confirm Password</mat-label>
|
|
||||||
<input matInput formControlName="confirmPassword" [type]="hidePassword ? 'password' : 'text'" required>
|
|
||||||
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
|
||||||
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-error *ngIf="confirmPassword?.errors?.['required']">
|
|
||||||
Please confirm your password
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="forgotPasswordForm.errors?.['mismatch'] && confirmPassword?.touched">
|
|
||||||
Passwords do not match
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
<!--
|
|
||||||
<div class="login">
|
|
||||||
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<button mat-raised-button color="primary" type="submit"
|
|
||||||
[disabled]="forgotPasswordForm.invalid || isLoading">
|
|
||||||
<span *ngIf="!isLoading">Submit</span>
|
|
||||||
<mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-section">
|
|
||||||
<h3>ATA Carnet</h3>
|
|
||||||
<p>
|
|
||||||
Also known as the "Merchandise Passport," is an international customs document that simplifies temporary
|
|
||||||
exports to over 79 countries and territories.
|
|
||||||
It allows businesses to explore new markets, showcase products at trade shows, and attend global conferences
|
|
||||||
without paying duties or taxes.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
It simplifies customs procedures for the temporary movement of goods and allows goods to
|
|
||||||
enter Customs territories of the ATA Carnet system free of customs duties and taxes for up to
|
|
||||||
one year.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
/* forgot-password.component.scss */
|
|
||||||
.forgot-password-container {
|
|
||||||
display: flex;
|
|
||||||
min-height: 85vh;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
|
|
||||||
.forgot-password-card {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 500px;
|
|
||||||
padding: 2rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: white;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.forgot-password-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
|
|
||||||
mat-form-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login {
|
|
||||||
text-align: right;
|
|
||||||
margin-top: -1rem;
|
|
||||||
|
|
||||||
.login-link {
|
|
||||||
color: #666;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdc-icon-button {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// button {
|
|
||||||
// margin-top: 1rem;
|
|
||||||
// padding: 0.5rem;
|
|
||||||
// font-size: 1rem;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-section {
|
|
||||||
flex: 1;
|
|
||||||
padding: 4rem;
|
|
||||||
background-color: #597b7c;
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.6;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.forgot-password-container {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.forgot-password-card {
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-section {
|
|
||||||
padding: 2rem;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { FormGroup, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
|
||||||
import { AuthService } from '../core/services/common/auth.service';
|
|
||||||
import { NotificationService } from '../core/services/common/notification.service';
|
|
||||||
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-forgot-password',
|
|
||||||
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
|
|
||||||
templateUrl: './forgot-password.component.html',
|
|
||||||
styleUrl: './forgot-password.component.scss'
|
|
||||||
})
|
|
||||||
export class ForgotPasswordComponent {
|
|
||||||
forgotPasswordRequestCompleted: boolean = false;
|
|
||||||
forgotPasswordRequestForm: FormGroup;
|
|
||||||
forgotPasswordForm: FormGroup;
|
|
||||||
isLoading = false;
|
|
||||||
hidePassword = true;
|
|
||||||
token: string | null = null;
|
|
||||||
|
|
||||||
private fb = inject(FormBuilder);
|
|
||||||
private authService = inject(AuthService);
|
|
||||||
private router = inject(Router);
|
|
||||||
private notificationService = inject(NotificationService);
|
|
||||||
private errorHandler = inject(ApiErrorHandlerService);
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.forgotPasswordRequestForm = this.fb.group({
|
|
||||||
email: ['', [Validators.required, Validators.email]]
|
|
||||||
});
|
|
||||||
|
|
||||||
this.forgotPasswordForm = this.fb.group({
|
|
||||||
email: ['', [Validators.required, Validators.email]],
|
|
||||||
password: ['', [Validators.required, Validators.minLength(8),
|
|
||||||
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
|
|
||||||
]],
|
|
||||||
confirmPassword: ['', [Validators.required]]
|
|
||||||
}, { validator: this.passwordMatchValidator });
|
|
||||||
|
|
||||||
// // Get token from URL if present
|
|
||||||
this.token = this.route.snapshot.paramMap.get('token');
|
|
||||||
|
|
||||||
// Check if token is null or empty
|
|
||||||
if (this.token && this.token.trim() !== '') {
|
|
||||||
this.forgotPasswordRequestCompleted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onRequest(): void {
|
|
||||||
if (this.forgotPasswordRequestForm.invalid) {
|
|
||||||
this.forgotPasswordRequestForm.markAllAsTouched();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isLoading = true;
|
|
||||||
const { email } = this.forgotPasswordRequestForm.value;
|
|
||||||
|
|
||||||
this.authService.sendEmail(email, 'FORGOT_PASSWORD').subscribe({
|
|
||||||
next: () => {
|
|
||||||
this.notificationService.showSuccess('A mail has been sent successfully to your email to setup a new password.');
|
|
||||||
this.isLoading = false;
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
this.isLoading = false;
|
|
||||||
let errorMessage = this.errorHandler.handleApiError(error, `Request failed. Please try again.`);
|
|
||||||
this.notificationService.showError(errorMessage);
|
|
||||||
console.error('Forgot password request failed:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit(): void {
|
|
||||||
if (this.forgotPasswordForm.invalid) {
|
|
||||||
this.forgotPasswordForm.markAllAsTouched();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isLoading = true;
|
|
||||||
const { email, password } = this.forgotPasswordForm.value;
|
|
||||||
|
|
||||||
this.authService.forgotPassword(email, password, this.token!).subscribe({
|
|
||||||
next: () => {
|
|
||||||
this.notificationService.showSuccess('Password reset successfully');
|
|
||||||
this.router.navigate(['/login']);
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
this.isLoading = false;
|
|
||||||
let errorMessage = this.errorHandler.handleApiError(error, `Password request failed. Please try again.`);
|
|
||||||
this.notificationService.showError(errorMessage);
|
|
||||||
console.error('Forgot password request failed:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private passwordMatchValidator(formGroup: FormGroup) {
|
|
||||||
const password = formGroup.get('password')?.value;
|
|
||||||
const confirmPassword = formGroup.get('confirmPassword')?.value;
|
|
||||||
return password === confirmPassword ? null : { mismatch: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
get emailRequest() {
|
|
||||||
return this.forgotPasswordRequestForm.get('email');
|
|
||||||
}
|
|
||||||
|
|
||||||
get email() {
|
|
||||||
return this.forgotPasswordForm.get('email');
|
|
||||||
}
|
|
||||||
|
|
||||||
get password() {
|
|
||||||
return this.forgotPasswordForm.get('password');
|
|
||||||
}
|
|
||||||
|
|
||||||
get confirmPassword() {
|
|
||||||
return this.forgotPasswordForm.get('confirmPassword');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -17,13 +17,6 @@
|
|||||||
<mat-spinner diameter="50"></mat-spinner>
|
<mat-spinner diameter="50"></mat-spinner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-actions">
|
|
||||||
<button mat-raised-button color="accent" (click)="exportData()" *ngIf="dataSource.data.length > 0"
|
|
||||||
matTooltip="Export to Excel" [disabled]="dataSource.data.length === 0">
|
|
||||||
<mat-icon>download</mat-icon> Export
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container mat-elevation-z8" *ngIf="showTable">
|
<div class="table-container mat-elevation-z8" *ngIf="showTable">
|
||||||
<table mat-table [dataSource]="dataSource" matSort>
|
<table mat-table [dataSource]="dataSource" matSort>
|
||||||
<ng-container matColumnDef="applicationName">
|
<ng-container matColumnDef="applicationName">
|
||||||
|
|||||||
@ -10,15 +10,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: end;
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { CarnetStatus } from '../core/models/carnet-status';
|
|||||||
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
||||||
import { CommonService } from '../core/services/common/common.service';
|
import { CommonService } from '../core/services/common/common.service';
|
||||||
import { NotificationService } from '../core/services/common/notification.service';
|
import { NotificationService } from '../core/services/common/notification.service';
|
||||||
import * as XLSX from 'xlsx';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
@ -104,40 +103,6 @@ export class HomeComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exportData() {
|
exportData() {
|
||||||
try {
|
|
||||||
// Prepare worksheet data
|
|
||||||
const worksheetData = this.prepareDownloadData();
|
|
||||||
|
|
||||||
// Create workbook and worksheet
|
|
||||||
const workbook = XLSX.utils.book_new();
|
|
||||||
const worksheet = XLSX.utils.json_to_sheet(worksheetData);
|
|
||||||
|
|
||||||
// Add worksheet to workbook
|
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Carnet Data');
|
|
||||||
|
|
||||||
// Generate Excel file
|
|
||||||
const fileName = `carnet_data_${new Date().toISOString()}.xlsx`;
|
|
||||||
XLSX.writeFile(workbook, fileName);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error downloading goods items:', error);
|
|
||||||
this.notificationService.showError('Failed to download Excel file');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareDownloadData(): any[] {
|
|
||||||
return this.dataSource.data.map(item => ({
|
|
||||||
'Application Name': item.applicationName,
|
|
||||||
'Holder Name': item.holderName,
|
|
||||||
'Carnet Number': item.carnetNumber,
|
|
||||||
'US Sets': item.usSets,
|
|
||||||
'Foreign Sets': item.foreignSets,
|
|
||||||
'Transit Sets': item.transitSets,
|
|
||||||
'Carnet Value': item.carnetValue,
|
|
||||||
'Issue Date': item.issueDate ? new Date(item.issueDate).toLocaleDateString() : '',
|
|
||||||
'Expiry Date': item.expiryDate ? new Date(item.expiryDate).toLocaleDateString() : '',
|
|
||||||
'Order Type': item.orderType,
|
|
||||||
'Carnet Status': this.getCarnetStatusLabel(item.carnetStatus)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCarnetStatusLabel(value: string): string {
|
getCarnetStatusLabel(value: string): string {
|
||||||
|
|||||||
@ -26,12 +26,8 @@
|
|||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="additional-options">
|
<div class="forgot-password">
|
||||||
<a href="#" routerLink="/forgot-password" class="additional-options-link">Forgot your password?</a>
|
<a href="#" class="forgot-link">Forgot your password?</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="additional-options">
|
|
||||||
<a href="#" routerLink="/register" class="additional-options-link">New User? Register</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button mat-raised-button color="primary" type="submit" [disabled]="!loginForm.valid">
|
<button mat-raised-button color="primary" type="submit" [disabled]="!loginForm.valid">
|
||||||
|
|||||||
@ -46,11 +46,11 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.additional-options {
|
.forgot-password {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin-top: -1rem;
|
margin-top: -1rem;
|
||||||
|
|
||||||
.additional-options-link {
|
.forgot-link {
|
||||||
color: #666;
|
color: #666;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|||||||
@ -6,11 +6,10 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
||||||
import { User } from '../core/models/user';
|
import { User } from '../core/models/user';
|
||||||
import { UserService } from '../core/services/common/user.service';
|
import { UserService } from '../core/services/common/user.service';
|
||||||
import { RouterModule } from '@angular/router';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule, RouterModule],
|
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule],
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.scss'
|
styleUrl: './login.component.scss'
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
<div class="registration-container">
|
|
||||||
<div class="registration-card">
|
|
||||||
<h3 class="subtitle">User Registration</h3>
|
|
||||||
|
|
||||||
<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()" class="registration-form">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Email</mat-label>
|
|
||||||
<input matInput formControlName="email" type="email" required>
|
|
||||||
<mat-icon matSuffix>email</mat-icon>
|
|
||||||
<mat-error *ngIf="email?.errors?.['required']">
|
|
||||||
Email is required
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="email?.errors?.['email']">
|
|
||||||
Please enter a valid email
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Password</mat-label>
|
|
||||||
<input matInput formControlName="password" [type]="hidePassword ? 'password' : 'text'" required>
|
|
||||||
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
|
||||||
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-error *ngIf="password?.errors?.['required']">
|
|
||||||
Password is required
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="password?.errors?.['minlength']">
|
|
||||||
Password must be at least 8 characters
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="password?.errors?.['pattern']">
|
|
||||||
Password must contain at least one uppercase letter, one lowercase letter, one number, and one
|
|
||||||
special character
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Confirm Password</mat-label>
|
|
||||||
<input matInput formControlName="confirmPassword" [type]="hidePassword ? 'password' : 'text'" required>
|
|
||||||
<button type="button" mat-icon-button matSuffix (click)="hidePassword = !hidePassword">
|
|
||||||
<mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-error *ngIf="confirmPassword?.errors?.['required']">
|
|
||||||
Please confirm your password
|
|
||||||
</mat-error>
|
|
||||||
<mat-error *ngIf="registrationForm.errors?.['mismatch'] && confirmPassword?.touched">
|
|
||||||
Passwords do not match
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div class="login">
|
|
||||||
<a href="#" routerLink="/login" class="login-link">Already have an account? Login</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button mat-raised-button color="primary" type="submit" [disabled]="registrationForm.invalid || isLoading">
|
|
||||||
<span *ngIf="!isLoading">Register</span>
|
|
||||||
<mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-section">
|
|
||||||
<h3>ATA Carnet</h3>
|
|
||||||
<p>
|
|
||||||
Also known as the "Merchandise Passport," is an international customs document that simplifies temporary
|
|
||||||
exports to over 79 countries and territories.
|
|
||||||
It allows businesses to explore new markets, showcase products at trade shows, and attend global conferences
|
|
||||||
without paying duties or taxes.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
It simplifies customs procedures for the temporary movement of goods and allows goods to
|
|
||||||
enter Customs territories of the ATA Carnet system free of customs duties and taxes for up to
|
|
||||||
one year.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
/* registration.component.scss */
|
|
||||||
.registration-container {
|
|
||||||
display: flex;
|
|
||||||
min-height: 85vh;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
|
|
||||||
.registration-card {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 500px;
|
|
||||||
padding: 2rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: white;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.registration-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
|
|
||||||
mat-form-field {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login {
|
|
||||||
text-align: right;
|
|
||||||
margin-top: -1rem;
|
|
||||||
|
|
||||||
.login-link {
|
|
||||||
color: #666;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdc-icon-button {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// button {
|
|
||||||
// margin-top: 1rem;
|
|
||||||
// padding: 0.5rem;
|
|
||||||
// font-size: 1rem;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-section {
|
|
||||||
flex: 1;
|
|
||||||
padding: 4rem;
|
|
||||||
background-color: #597b7c;
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
line-height: 1.6;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.registration-container {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.registration-card {
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-section {
|
|
||||||
padding: 2rem;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
import { Component, inject } from '@angular/core';
|
|
||||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { AuthService } from '../core/services/common/auth.service';
|
|
||||||
import { NotificationService } from '../core/services/common/notification.service';
|
|
||||||
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
|
||||||
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-register',
|
|
||||||
imports: [AngularMaterialModule, ReactiveFormsModule, CommonModule],
|
|
||||||
templateUrl: './register.component.html',
|
|
||||||
styleUrl: './register.component.scss'
|
|
||||||
})
|
|
||||||
export class RegisterComponent {
|
|
||||||
|
|
||||||
registrationForm: FormGroup;
|
|
||||||
isLoading = false;
|
|
||||||
hidePassword = true;
|
|
||||||
|
|
||||||
// tokenError: boolean = false;
|
|
||||||
//token: string | null = null;
|
|
||||||
|
|
||||||
private fb = inject(FormBuilder);
|
|
||||||
private authService = inject(AuthService);
|
|
||||||
private router = inject(Router);
|
|
||||||
private route = inject(ActivatedRoute);
|
|
||||||
private notificationService = inject(NotificationService);
|
|
||||||
private errorHandler = inject(ApiErrorHandlerService);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.registrationForm = this.fb.group({
|
|
||||||
email: ['', [Validators.required, Validators.email]],
|
|
||||||
password: ['', [Validators.required, Validators.minLength(8),
|
|
||||||
Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/)
|
|
||||||
]],
|
|
||||||
confirmPassword: ['', [Validators.required]]
|
|
||||||
}, { validator: this.passwordMatchValidator });
|
|
||||||
|
|
||||||
// // Get token from URL if present
|
|
||||||
// this.token = this.route.snapshot.paramMap.get('token');
|
|
||||||
|
|
||||||
// // Check if token is null or empty
|
|
||||||
// if (!this.token || this.token.trim() === '') {
|
|
||||||
// this.tokenError = true;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit(): void {
|
|
||||||
if (this.registrationForm.invalid) {
|
|
||||||
this.registrationForm.markAllAsTouched();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isLoading = true;
|
|
||||||
const { email, password } = this.registrationForm.value;
|
|
||||||
|
|
||||||
this.authService.register(email, password).subscribe({
|
|
||||||
next: () => {
|
|
||||||
this.notificationService.showSuccess('Registration successful');
|
|
||||||
this.router.navigate(['/login']);
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
this.isLoading = false;
|
|
||||||
let errorMessage = `Registration failed. Please request a new registration link from your administrator.`;
|
|
||||||
this.notificationService.showError(errorMessage);
|
|
||||||
console.error('Registration failed:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private passwordMatchValidator(formGroup: FormGroup) {
|
|
||||||
const password = formGroup.get('password')?.value;
|
|
||||||
const confirmPassword = formGroup.get('confirmPassword')?.value;
|
|
||||||
return password === confirmPassword ? null : { mismatch: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
get email() {
|
|
||||||
return this.registrationForm.get('email');
|
|
||||||
}
|
|
||||||
|
|
||||||
get password() {
|
|
||||||
return this.registrationForm.get('password');
|
|
||||||
}
|
|
||||||
|
|
||||||
get confirmPassword() {
|
|
||||||
return this.registrationForm.get('confirmPassword');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user