Compare commits
2 Commits
e226518587
...
a1f31faf73
| Author | SHA1 | Date | |
|---|---|---|---|
| a1f31faf73 | |||
| 1fb87c9353 |
@ -123,9 +123,6 @@
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"deploy": {
|
||||
"builder": "angular-cli-ghpages:deploy"
|
||||
}
|
||||
},
|
||||
"i18n": {
|
||||
|
||||
1013
package-lock.json
generated
1013
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@
|
||||
"ngx-cookie-service": "^20.0.1",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -37,7 +38,6 @@
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^18.18.0",
|
||||
"angular-cli-ghpages": "^2.0.3",
|
||||
"jasmine-core": "~5.6.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
|
||||
@ -6,9 +6,13 @@ import { AddServiceProviderComponent } from './service-provider/add/add-service-
|
||||
import { EditServiceProviderComponent } from './service-provider/edit/edit-service-provider.component';
|
||||
import { NotFoundComponent } from './shared/components/not-found/not-found.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 = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
{ path: 'forgot-password', component: ForgotPasswordComponent },
|
||||
{ path: 'usersettings', component: UserSettingsComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'service-provider/:id', component: EditServiceProviderComponent, canActivate: [AuthGuard] },
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
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,6 +7,7 @@ import { AuthService } from '../services/common/auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
private excludedUrls = ['/register', '/forgot-password'];
|
||||
|
||||
private isRefreshing = false;
|
||||
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
|
||||
@ -17,6 +18,11 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
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
|
||||
request = request.clone({
|
||||
withCredentials: true
|
||||
|
||||
@ -13,6 +13,7 @@ export interface Menu {
|
||||
export interface UserDetail {
|
||||
spid: number;
|
||||
clientid: number;
|
||||
locationid: number;
|
||||
urlKey: string;
|
||||
logoName: string;
|
||||
themeName: string;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
@ -45,4 +45,33 @@ 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,9 +12,6 @@ export class UserService {
|
||||
private apiUrl = environment.apiUrl;
|
||||
private apiDb = environment.apiDb;
|
||||
|
||||
private spid: number = 0;
|
||||
private clientid: number = 0;
|
||||
|
||||
userDetailsSignal = signal<User>({});
|
||||
|
||||
private readonly USER_EMAIL_KEY = 'CurrentUserEmail';
|
||||
@ -98,23 +95,18 @@ export class UserService {
|
||||
}
|
||||
|
||||
getUserSpid(): number {
|
||||
if (this.spid === 0) {
|
||||
const userDetails = this.getUserDetails();
|
||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.spid) {
|
||||
this.spid = userDetails.userDetails.spid;
|
||||
}
|
||||
}
|
||||
return this.spid;
|
||||
return userDetails?.userDetails?.spid ?? 0;
|
||||
}
|
||||
|
||||
getUserClientid(): number {
|
||||
if (this.clientid === 0) {
|
||||
const userDetails = this.getUserDetails();
|
||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.clientid) {
|
||||
this.clientid = userDetails.userDetails.clientid;
|
||||
return userDetails?.userDetails?.clientid ?? 0;
|
||||
}
|
||||
}
|
||||
return this.clientid;
|
||||
|
||||
getUserLocationid(): number {
|
||||
const userDetails = this.getUserDetails();
|
||||
return userDetails?.userDetails?.locationid ?? 0;
|
||||
}
|
||||
|
||||
private mapToUser(data: any): User {
|
||||
@ -141,6 +133,7 @@ export class UserService {
|
||||
return {
|
||||
spid: userDetails.SPID,
|
||||
clientid: userDetails.CLIENTID,
|
||||
locationid: userDetails.LOCATIONID,
|
||||
urlKey: userDetails.ENCURLKEY,
|
||||
logoName: userDetails.LOGONAME,
|
||||
themeName: userDetails.THEMENAME
|
||||
|
||||
105
src/app/forgot-password/forgot-password.component.html
Normal file
105
src/app/forgot-password/forgot-password.component.html
Normal file
@ -0,0 +1,105 @@
|
||||
<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>
|
||||
95
src/app/forgot-password/forgot-password.component.scss
Normal file
95
src/app/forgot-password/forgot-password.component.scss
Normal file
@ -0,0 +1,95 @@
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/app/forgot-password/forgot-password.component.ts
Normal file
120
src/app/forgot-password/forgot-password.component.ts
Normal file
@ -0,0 +1,120 @@
|
||||
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,6 +17,13 @@
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</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">
|
||||
<table mat-table [dataSource]="dataSource" matSort>
|
||||
<ng-container matColumnDef="applicationName">
|
||||
|
||||
@ -10,6 +10,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
|
||||
button {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
|
||||
@ -14,6 +14,7 @@ import { CarnetStatus } from '../core/models/carnet-status';
|
||||
import { ApiErrorHandlerService } from '../core/services/common/api-error-handler.service';
|
||||
import { CommonService } from '../core/services/common/common.service';
|
||||
import { NotificationService } from '../core/services/common/notification.service';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
@ -103,6 +104,40 @@ export class HomeComponent {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@ -26,8 +26,12 @@
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="forgot-password">
|
||||
<a href="#" class="forgot-link">Forgot your password?</a>
|
||||
<div class="additional-options">
|
||||
<a href="#" routerLink="/forgot-password" class="additional-options-link">Forgot your password?</a>
|
||||
</div>
|
||||
|
||||
<div class="additional-options">
|
||||
<a href="#" routerLink="/register" class="additional-options-link">New User? Register</a>
|
||||
</div>
|
||||
|
||||
<button mat-raised-button color="primary" type="submit" [disabled]="!loginForm.valid">
|
||||
|
||||
@ -46,11 +46,11 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
.additional-options {
|
||||
text-align: right;
|
||||
margin-top: -1rem;
|
||||
|
||||
.forgot-link {
|
||||
.additional-options-link {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
|
||||
@ -6,10 +6,11 @@ import { CommonModule } from '@angular/common';
|
||||
import { AngularMaterialModule } from '../shared/module/angular-material.module';
|
||||
import { User } from '../core/models/user';
|
||||
import { UserService } from '../core/services/common/user.service';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule],
|
||||
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule, RouterModule],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss'
|
||||
})
|
||||
|
||||
76
src/app/register/register.component.html
Normal file
76
src/app/register/register.component.html
Normal file
@ -0,0 +1,76 @@
|
||||
<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>
|
||||
95
src/app/register/register.component.scss
Normal file
95
src/app/register/register.component.scss
Normal file
@ -0,0 +1,95 @@
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/app/register/register.component.ts
Normal file
90
src/app/register/register.component.ts
Normal file
@ -0,0 +1,90 @@
|
||||
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