Compare commits

...

2 Commits

Author SHA1 Message Date
a1f31faf73 package updates 2025-07-10 23:52:14 -03:00
1fb87c9353 register, forgot password and home page updates 2025-07-10 22:46:27 -03:00
21 changed files with 796 additions and 961 deletions

View File

@ -123,9 +123,6 @@
],
"scripts": []
}
},
"deploy": {
"builder": "angular-cli-ghpages:deploy"
}
},
"i18n": {

1013
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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] },

View File

@ -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;
}
}

View File

@ -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

View File

@ -13,6 +13,7 @@ export interface Menu {
export interface UserDetail {
spid: number;
clientid: number;
locationid: number;
urlKey: string;
logoName: string;
themeName: string;

View File

@ -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 }
);
}
}

View File

@ -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;
const userDetails = this.getUserDetails();
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 this.clientid;
const userDetails = this.getUserDetails();
return userDetails?.userDetails?.clientid ?? 0;
}
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

View 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>

View 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;
}
}
}

View 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');
}
}

View File

@ -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">

View File

@ -10,6 +10,15 @@
}
}
.table-actions {
display: flex;
justify-content: end;
button {
margin-bottom: 16px;
}
}
.table-container {
position: relative;
overflow: auto;

View File

@ -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 {

View File

@ -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">

View File

@ -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;

View File

@ -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'
})

View 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>

View 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;
}
}
}

View 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');
}
}