register, forgot password and home page updates
This commit is contained in:
parent
e226518587
commit
1fb87c9353
104
package-lock.json
generated
104
package-lock.json
generated
@ -25,6 +25,7 @@
|
|||||||
"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": {
|
||||||
@ -3751,6 +3752,15 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/adler-32": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
||||||
@ -4587,6 +4597,19 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/cfb": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"adler-32": "~1.3.0",
|
||||||
|
"crc-32": "~1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@ -4804,6 +4827,15 @@
|
|||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codepage": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@ -5009,6 +5041,18 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crc-32": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"crc32": "bin/crc32.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -5811,6 +5855,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/frac": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
@ -9623,6 +9676,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/ssf": {
|
||||||
|
"version": "0.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||||
|
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"frac": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ssri": {
|
"node_modules/ssri": {
|
||||||
"version": "12.0.0",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz",
|
||||||
@ -10390,6 +10455,24 @@
|
|||||||
"which": "bin/which"
|
"which": "bin/which"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wmf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/word": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||||
@ -10572,6 +10655,27 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xlsx": {
|
||||||
|
"version": "0.18.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||||
|
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"adler-32": "~1.3.0",
|
||||||
|
"cfb": "~1.2.1",
|
||||||
|
"codepage": "~1.15.0",
|
||||||
|
"crc-32": "~1.2.1",
|
||||||
|
"ssf": "~0.11.2",
|
||||||
|
"wmf": "~1.0.1",
|
||||||
|
"word": "~0.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"xlsx": "bin/xlsx.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
"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": {
|
||||||
|
|||||||
@ -6,9 +6,13 @@ 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] },
|
||||||
|
|||||||
@ -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()
|
@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);
|
||||||
@ -17,6 +18,11 @@ 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,6 +13,7 @@ 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 } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } 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,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 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';
|
||||||
@ -98,23 +95,18 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUserSpid(): number {
|
getUserSpid(): number {
|
||||||
if (this.spid === 0) {
|
const userDetails = this.getUserDetails();
|
||||||
const userDetails = this.getUserDetails();
|
return userDetails?.userDetails?.spid ?? 0;
|
||||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.spid) {
|
|
||||||
this.spid = userDetails.userDetails.spid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.spid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserClientid(): number {
|
getUserClientid(): number {
|
||||||
if (this.clientid === 0) {
|
const userDetails = this.getUserDetails();
|
||||||
const userDetails = this.getUserDetails();
|
return userDetails?.userDetails?.clientid ?? 0;
|
||||||
if (userDetails && userDetails.userDetails && userDetails.userDetails.clientid) {
|
}
|
||||||
this.clientid = userDetails.userDetails.clientid;
|
|
||||||
}
|
getUserLocationid(): number {
|
||||||
}
|
const userDetails = this.getUserDetails();
|
||||||
return this.clientid;
|
return userDetails?.userDetails?.locationid ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapToUser(data: any): User {
|
private mapToUser(data: any): User {
|
||||||
@ -141,6 +133,7 @@ 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
|
||||||
|
|||||||
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>
|
<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,6 +10,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ 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',
|
||||||
@ -103,6 +104,40 @@ 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,8 +26,12 @@
|
|||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="forgot-password">
|
<div class="additional-options">
|
||||||
<a href="#" class="forgot-link">Forgot your password?</a>
|
<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>
|
</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%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.forgot-password {
|
.additional-options {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin-top: -1rem;
|
margin-top: -1rem;
|
||||||
|
|
||||||
.forgot-link {
|
.additional-options-link {
|
||||||
color: #666;
|
color: #666;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|||||||
@ -6,10 +6,11 @@ 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],
|
imports: [ReactiveFormsModule, CommonModule, AngularMaterialModule, RouterModule],
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.scss'
|
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