@@ -0,0 +1,17 @@ | |||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. | |||||
# For additional information regarding the format and rule options, please see: | |||||
# https://github.com/browserslist/browserslist#queries | |||||
# For the full list of supported browsers by the Angular framework, please see: | |||||
# https://angular.io/guide/browser-support | |||||
# You can see what browsers were selected by your queries by running: | |||||
# npx browserslist | |||||
last 1 Chrome version | |||||
last 1 Firefox version | |||||
last 2 Edge major versions | |||||
last 2 Safari major versions | |||||
last 2 iOS major versions | |||||
Firefox ESR | |||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. |
@@ -0,0 +1,16 @@ | |||||
# Editor configuration, see https://editorconfig.org | |||||
root = true | |||||
[*] | |||||
charset = utf-8 | |||||
indent_style = space | |||||
indent_size = 2 | |||||
insert_final_newline = true | |||||
trim_trailing_whitespace = true | |||||
[*.ts] | |||||
quote_type = single | |||||
[*.md] | |||||
max_line_length = off | |||||
trim_trailing_whitespace = false |
@@ -0,0 +1,45 @@ | |||||
# See http://help.github.com/ignore-files/ for more about ignoring files. | |||||
# compiled output | |||||
/dist | |||||
/tmp | |||||
/out-tsc | |||||
# Only exists if Bazel was run | |||||
/bazel-out | |||||
# dependencies | |||||
/node_modules | |||||
# profiling files | |||||
chrome-profiler-events*.json | |||||
# IDEs and editors | |||||
/.idea | |||||
.project | |||||
.classpath | |||||
.c9/ | |||||
*.launch | |||||
.settings/ | |||||
*.sublime-workspace | |||||
# IDE - VSCode | |||||
.vscode/* | |||||
!.vscode/settings.json | |||||
!.vscode/tasks.json | |||||
!.vscode/launch.json | |||||
!.vscode/extensions.json | |||||
.history/* | |||||
# misc | |||||
/.sass-cache | |||||
/connect.lock | |||||
/coverage | |||||
/libpeerconnection.log | |||||
npm-debug.log | |||||
yarn-error.log | |||||
testem.log | |||||
/typings | |||||
# System Files | |||||
.DS_Store | |||||
Thumbs.db |
@@ -0,0 +1,116 @@ | |||||
{ | |||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", | |||||
"cli": { | |||||
"analytics": "24d3339d-3296-4758-a2bf-690d684fdf7c" | |||||
}, | |||||
"version": 1, | |||||
"newProjectRoot": "projects", | |||||
"projects": { | |||||
"eni": { | |||||
"projectType": "application", | |||||
"schematics": { | |||||
"@schematics/angular:component": { | |||||
"style": "sass" | |||||
}, | |||||
"@schematics/angular:application": { | |||||
"strict": true | |||||
} | |||||
}, | |||||
"root": "", | |||||
"sourceRoot": "src", | |||||
"prefix": "app", | |||||
"architect": { | |||||
"build": { | |||||
"builder": "@angular-devkit/build-angular:browser", | |||||
"options": { | |||||
"outputPath": "dist/eni", | |||||
"index": "src/index.html", | |||||
"main": "src/main.ts", | |||||
"polyfills": "src/polyfills.ts", | |||||
"tsConfig": "tsconfig.app.json", | |||||
"inlineStyleLanguage": "sass", | |||||
"assets": [ | |||||
"src/favicon.ico", | |||||
"src/assets" | |||||
], | |||||
"styles": [ | |||||
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", | |||||
"src/styles.sass" | |||||
], | |||||
"scripts": [] | |||||
}, | |||||
"configurations": { | |||||
"production": { | |||||
"budgets": [ | |||||
{ | |||||
"type": "initial", | |||||
"maximumWarning": "500kb", | |||||
"maximumError": "1mb" | |||||
}, | |||||
{ | |||||
"type": "anyComponentStyle", | |||||
"maximumWarning": "2kb", | |||||
"maximumError": "4kb" | |||||
} | |||||
], | |||||
"fileReplacements": [ | |||||
{ | |||||
"replace": "src/environments/environment.ts", | |||||
"with": "src/environments/environment.prod.ts" | |||||
} | |||||
], | |||||
"outputHashing": "all" | |||||
}, | |||||
"development": { | |||||
"buildOptimizer": false, | |||||
"optimization": false, | |||||
"vendorChunk": true, | |||||
"extractLicenses": false, | |||||
"sourceMap": true, | |||||
"namedChunks": true | |||||
} | |||||
}, | |||||
"defaultConfiguration": "production" | |||||
}, | |||||
"serve": { | |||||
"builder": "@angular-devkit/build-angular:dev-server", | |||||
"configurations": { | |||||
"production": { | |||||
"browserTarget": "eni:build:production" | |||||
}, | |||||
"development": { | |||||
"browserTarget": "eni:build:development" | |||||
} | |||||
}, | |||||
"defaultConfiguration": "development" | |||||
}, | |||||
"extract-i18n": { | |||||
"builder": "@angular-devkit/build-angular:extract-i18n", | |||||
"options": { | |||||
"browserTarget": "eni:build" | |||||
} | |||||
}, | |||||
"test": { | |||||
"builder": "@angular-devkit/build-angular:karma", | |||||
"options": { | |||||
"main": "src/test.ts", | |||||
"polyfills": "src/polyfills.ts", | |||||
"tsConfig": "tsconfig.spec.json", | |||||
"karmaConfig": "karma.conf.js", | |||||
"inlineStyleLanguage": "sass", | |||||
"assets": [ | |||||
"src/favicon.ico", | |||||
"src/assets" | |||||
], | |||||
"styles": [ | |||||
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", | |||||
"src/styles.sass" | |||||
], | |||||
"scripts": [] | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"defaultProject": "eni" | |||||
} |
@@ -0,0 +1,44 @@ | |||||
// Karma configuration file, see link for more information | |||||
// https://karma-runner.github.io/1.0/config/configuration-file.html | |||||
module.exports = function (config) { | |||||
config.set({ | |||||
basePath: '', | |||||
frameworks: ['jasmine', '@angular-devkit/build-angular'], | |||||
plugins: [ | |||||
require('karma-jasmine'), | |||||
require('karma-chrome-launcher'), | |||||
require('karma-jasmine-html-reporter'), | |||||
require('karma-coverage'), | |||||
require('@angular-devkit/build-angular/plugins/karma') | |||||
], | |||||
client: { | |||||
jasmine: { | |||||
// you can add configuration options for Jasmine here | |||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html | |||||
// for example, you can disable the random execution with `random: false` | |||||
// or set a specific seed with `seed: 4321` | |||||
}, | |||||
clearContext: false // leave Jasmine Spec Runner output visible in browser | |||||
}, | |||||
jasmineHtmlReporter: { | |||||
suppressAll: true // removes the duplicated traces | |||||
}, | |||||
coverageReporter: { | |||||
dir: require('path').join(__dirname, './coverage/eni'), | |||||
subdir: '.', | |||||
reporters: [ | |||||
{ type: 'html' }, | |||||
{ type: 'text-summary' } | |||||
] | |||||
}, | |||||
reporters: ['progress', 'kjhtml'], | |||||
port: 9876, | |||||
colors: true, | |||||
logLevel: config.LOG_INFO, | |||||
autoWatch: true, | |||||
browsers: ['Chrome'], | |||||
singleRun: false, | |||||
restartOnFileChange: true | |||||
}); | |||||
}; |
@@ -0,0 +1,42 @@ | |||||
{ | |||||
"name": "eni", | |||||
"version": "0.0.0", | |||||
"scripts": { | |||||
"ng": "ng", | |||||
"start": "ng serve", | |||||
"build": "ng build", | |||||
"watch": "ng build --watch --configuration development", | |||||
"test": "ng test" | |||||
}, | |||||
"private": true, | |||||
"dependencies": { | |||||
"@angular/animations": "~12.2.0", | |||||
"@angular/cdk": "^12.2.13", | |||||
"@angular/common": "~12.2.0", | |||||
"@angular/compiler": "~12.2.0", | |||||
"@angular/core": "~12.2.0", | |||||
"@angular/forms": "~12.2.0", | |||||
"@angular/material": "^12.2.13", | |||||
"@angular/platform-browser": "~12.2.0", | |||||
"@angular/platform-browser-dynamic": "~12.2.0", | |||||
"@angular/router": "~12.2.0", | |||||
"bootstrap": "^5.3.3", | |||||
"rxjs": "~6.6.0", | |||||
"tslib": "^2.3.0", | |||||
"zone.js": "~0.11.4" | |||||
}, | |||||
"devDependencies": { | |||||
"@angular-devkit/build-angular": "~12.2.12", | |||||
"@angular/cli": "~12.2.12", | |||||
"@angular/compiler-cli": "~12.2.0", | |||||
"@types/jasmine": "~3.8.0", | |||||
"@types/node": "^12.11.1", | |||||
"jasmine-core": "~3.8.0", | |||||
"karma": "~6.3.0", | |||||
"karma-chrome-launcher": "~3.1.0", | |||||
"karma-coverage": "~2.0.3", | |||||
"karma-jasmine": "~4.0.0", | |||||
"karma-jasmine-html-reporter": "~1.7.0", | |||||
"typescript": "~4.3.5" | |||||
} | |||||
} |
@@ -0,0 +1,48 @@ | |||||
import { Injectable } from '@angular/core'; | |||||
import { | |||||
Router, | |||||
CanActivate, | |||||
ActivatedRouteSnapshot, | |||||
RouterStateSnapshot, | |||||
} from '@angular/router'; | |||||
import { AuthenticationService } from '../_services/authentication.service'; | |||||
@Injectable({ providedIn: 'root' }) | |||||
export class AuthGuard implements CanActivate { | |||||
constructor( | |||||
private router: Router, | |||||
private authenticationService: AuthenticationService | |||||
) {} | |||||
canActivate( | |||||
route: ActivatedRouteSnapshot, | |||||
state: RouterStateSnapshot | |||||
): boolean { | |||||
const user = this.authenticationService.userValue; | |||||
if (user) { | |||||
// check if route is restricted by role | |||||
if ( | |||||
route.data['roles'] && | |||||
route.data['roles'].indexOf(user.role) === -1 | |||||
) { | |||||
// role not authorised so redirect to home page | |||||
this.router.navigate(['/']); | |||||
return false; | |||||
} | |||||
// authorised so return true | |||||
return true; | |||||
} | |||||
const userLogged = localStorage.getItem('loggedser') | |||||
if(userLogged == 'admin'){ | |||||
this.router.navigate(['/admin-login'], { queryParams: { returnUrl: state.url } }); | |||||
} else if(userLogged == 'user') { | |||||
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); | |||||
} | |||||
return false; | |||||
} | |||||
} |
@@ -0,0 +1,43 @@ | |||||
import { Injectable } from '@angular/core'; | |||||
import { | |||||
HttpRequest, | |||||
HttpHandler, | |||||
HttpEvent, | |||||
HttpInterceptor, | |||||
} from '@angular/common/http'; | |||||
import { Observable, throwError } from 'rxjs'; | |||||
import { catchError } from 'rxjs/operators'; | |||||
import { AuthService } from '../_services'; | |||||
@Injectable() | |||||
export class ErrorInterceptor implements HttpInterceptor { | |||||
constructor(private authenticationService: AuthService) {} | |||||
intercept( | |||||
request: HttpRequest<any>, | |||||
next: HttpHandler | |||||
): Observable<HttpEvent<any>> { | |||||
return next.handle(request).pipe( | |||||
catchError((err: any) => { | |||||
if (err.error && err.error.status_code === 401 || err.error.status_code === 400) { | |||||
if (err.error.message && | |||||
(err.error.message.includes('{}') || | |||||
err.error.message.includes(':') || | |||||
err.error.message.includes('(') || | |||||
err.error.message.includes(')') || | |||||
err.error.message.includes(',') || | |||||
err.error.message === "Token is expired")) { | |||||
return throwError("Session Expired"); | |||||
} else if (err.error.message) { | |||||
return throwError(err.error.message); | |||||
} | |||||
} else if (err.error && err.error.message) { | |||||
return throwError(err.error.message); | |||||
} | |||||
const error = err.statusText; | |||||
return throwError(error); | |||||
}) | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,3 @@ | |||||
export * from './auth.guard'; | |||||
export * from './error.interceptor'; | |||||
export * from './jwt.interceptor'; |
@@ -0,0 +1,45 @@ | |||||
import { Injectable } from '@angular/core'; | |||||
import { | |||||
HttpRequest, | |||||
HttpHandler, | |||||
HttpEvent, | |||||
HttpInterceptor, | |||||
} from '@angular/common/http'; | |||||
import { Observable } from 'rxjs'; | |||||
import { environment } from '../../environments/environment'; | |||||
import { AuthService } from '../_services'; | |||||
@Injectable() | |||||
export class JwtInterceptor implements HttpInterceptor { | |||||
constructor(private AuthService: AuthService) {} | |||||
intercept( | |||||
request: HttpRequest<any>, | |||||
next: HttpHandler | |||||
): Observable<HttpEvent<any>> { | |||||
// add auth header with jwt if user is logged in and request is to api url | |||||
//const token = localStorage.getItem('token'); | |||||
//const token1 = token?.slice(1, token.length - 1); | |||||
let token1: any = localStorage.getItem('token'); | |||||
let token; | |||||
if (token1) { | |||||
token = token1.replace(/['"]+/g, ''); | |||||
} else { | |||||
token = token1; | |||||
} | |||||
const user = this.AuthService.userValue; | |||||
const isLoggedIn = user && user.token; | |||||
const isApiUrl = request.url.startsWith(environment.apiUrl); | |||||
if (isLoggedIn && isApiUrl) { | |||||
request = request.clone({ | |||||
setHeaders: { | |||||
Authorization: `Bearer ${token}`, | |||||
}, | |||||
}); | |||||
} | |||||
return next.handle(request); | |||||
} | |||||
} |
@@ -0,0 +1,13 @@ | |||||
import { AbstractControl, ValidatorFn } from '@angular/forms'; | |||||
import { FormGroup } from '@angular/forms'; | |||||
export class customMobileValidationService { | |||||
static checkLimit(min: number, max: number): ValidatorFn { | |||||
return (c: AbstractControl): { [key: string]: boolean } | null => { | |||||
if (c.value && (isNaN(c.value) || c.value < min || c.value > max)) { | |||||
return { range: true }; | |||||
} | |||||
return null; | |||||
}; | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
import { FormGroup } from '@angular/forms'; | |||||
export function ConfirmPasswordValidator(controlName: string, matchingControlName: string) { | |||||
return (formGroup: FormGroup) => { | |||||
let control = formGroup.controls[controlName]; | |||||
let matchingControl = formGroup.controls[matchingControlName] | |||||
if ( | |||||
matchingControl.errors && | |||||
!matchingControl.errors ['confirmPasswordValidator'] | |||||
) { | |||||
return; | |||||
} | |||||
if (control.value !== matchingControl.value) { | |||||
matchingControl.setErrors({ confirmPasswordValidator: true }); | |||||
} else { | |||||
matchingControl.setErrors(null); | |||||
} | |||||
}; | |||||
} |
@@ -0,0 +1,16 @@ | |||||
import { Directive, ElementRef, HostListener } from '@angular/core'; | |||||
@Directive({ | |||||
selector: '[appNumberOnly]' | |||||
}) | |||||
export class NumberOnlyDirective { | |||||
constructor(private _el: ElementRef) { } | |||||
@HostListener('input', ['$event']) onInputChange(event: any) { | |||||
const initalValue = this._el.nativeElement.value; | |||||
this._el.nativeElement.value = initalValue.replace(/[^0-9]*/g, ''); | |||||
if ( initalValue !== this._el.nativeElement.value) { | |||||
event.stopPropagation(); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,2 @@ | |||||
export * from './role'; | |||||
export * from './user'; |
@@ -0,0 +1,7 @@ | |||||
export enum Role { | |||||
SUPERADMIN = 1, | |||||
DCP = 2, | |||||
ACP = 3, | |||||
SHO = 4, | |||||
IO = 5, | |||||
} |
@@ -0,0 +1,12 @@ | |||||
import { Role } from './role'; | |||||
export class User { | |||||
id!: number; | |||||
full_name!: string; | |||||
PIS_number!: number; | |||||
phone_number!: number; | |||||
email!: string; | |||||
district_id!: string; | |||||
role!: Role; | |||||
token?: any; | |||||
} |
@@ -0,0 +1,16 @@ | |||||
import { TestBed } from '@angular/core/testing'; | |||||
import { ApiService } from './api.service'; | |||||
describe('ApiService', () => { | |||||
let service: ApiService; | |||||
beforeEach(() => { | |||||
TestBed.configureTestingModule({}); | |||||
service = TestBed.inject(ApiService); | |||||
}); | |||||
it('should be created', () => { | |||||
expect(service).toBeTruthy(); | |||||
}); | |||||
}); |
@@ -0,0 +1,9 @@ | |||||
import { Injectable } from '@angular/core'; | |||||
@Injectable({ | |||||
providedIn: 'root' | |||||
}) | |||||
export class ApiService { | |||||
constructor() { } | |||||
} |
@@ -0,0 +1,16 @@ | |||||
import { TestBed } from '@angular/core/testing'; | |||||
import { AuthService } from './auth.service'; | |||||
describe('AuthService', () => { | |||||
let service: AuthService; | |||||
beforeEach(() => { | |||||
TestBed.configureTestingModule({}); | |||||
service = TestBed.inject(AuthService); | |||||
}); | |||||
it('should be created', () => { | |||||
expect(service).toBeTruthy(); | |||||
}); | |||||
}); |
@@ -0,0 +1,25 @@ | |||||
import { Injectable } from '@angular/core'; | |||||
import { BehaviorSubject, Observable } from 'rxjs'; | |||||
import { User } from '../_models'; | |||||
import { Router } from '@angular/router'; | |||||
import { HttpClient } from '@angular/common/http'; | |||||
@Injectable({ | |||||
providedIn: 'root' | |||||
}) | |||||
export class AuthService { | |||||
private userSubject: BehaviorSubject<User>; | |||||
public user: Observable<User>; | |||||
constructor(private router: Router, private http: HttpClient) { | |||||
const storedUser = localStorage.getItem('currentUser'); | |||||
this.userSubject = new BehaviorSubject<User>( | |||||
storedUser ? JSON.parse(storedUser) : null | |||||
); | |||||
this.user = this.userSubject.asObservable(); | |||||
} | |||||
public get userValue(): User { | |||||
return this.userSubject.value; | |||||
} | |||||
} |
@@ -0,0 +1,2 @@ | |||||
export * from './auth.service'; | |||||
export * from './api.service'; |
@@ -0,0 +1,42 @@ | |||||
<div class="loginWrap"> | |||||
<div class="container"> | |||||
<div class="row"> | |||||
<div class="col-md-6"></div> | |||||
<div class="col-md-6"> | |||||
<div class="loginBox" [formGroup]="loginForm" > | |||||
<h1> Login </h1> | |||||
<form action=""> | |||||
<div class="form-group"> | |||||
<label for="username">Username:</label> | |||||
<input type="text" class="form-control" formControlName="username" [ngClass]="{ 'is-invalid': submited && f.username.errors }" | |||||
> | |||||
<div *ngIf="submited && f.username.errors" class="invalid-feedback"> | |||||
<div *ngIf="f.username.errors.required">Username is required</div> | |||||
</div> | |||||
</div> | |||||
<div class="form-group"> | |||||
<label for="password">Password:</label> | |||||
<input type="Password" class="form-control" formControlName="password" [ngClass]="{ 'is-invalid': submited && f.password.errors }" | |||||
> | |||||
<div *ngIf="submited && f.password.errors" class="invalid-feedback"> | |||||
<div *ngIf="f.password.errors.required">Password is required</div> | |||||
<div *ngIf="f.password.errors.minlength"> | |||||
Password must be at least 6 characters | |||||
</div> | |||||
<div *ngIf="f.password.errors.maxlength"> | |||||
Username must not exceed 40 characters | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<button class="btn btn-success" (click)="onSubmit()" > LOGIN </button> | |||||
<p routerLink="/sign-up" > Sign up </p> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> |
@@ -0,0 +1,25 @@ | |||||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
import { LoginComponent } from './login.component'; | |||||
describe('LoginComponent', () => { | |||||
let component: LoginComponent; | |||||
let fixture: ComponentFixture<LoginComponent>; | |||||
beforeEach(async () => { | |||||
await TestBed.configureTestingModule({ | |||||
declarations: [ LoginComponent ] | |||||
}) | |||||
.compileComponents(); | |||||
}); | |||||
beforeEach(() => { | |||||
fixture = TestBed.createComponent(LoginComponent); | |||||
component = fixture.componentInstance; | |||||
fixture.detectChanges(); | |||||
}); | |||||
it('should create', () => { | |||||
expect(component).toBeTruthy(); | |||||
}); | |||||
}); |
@@ -0,0 +1,41 @@ | |||||
import { Component, OnInit } from '@angular/core'; | |||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | |||||
@Component({ | |||||
selector: 'app-login', | |||||
templateUrl: './login.component.html', | |||||
styleUrls: ['./login.component.sass'] | |||||
}) | |||||
export class LoginComponent implements OnInit { | |||||
public loginForm!: FormGroup | |||||
public submited: boolean = false | |||||
constructor(private formBuilder: FormBuilder) { } | |||||
ngOnInit(): void { | |||||
this.loginForm = this.formBuilder.group( | |||||
{ | |||||
username: ['', Validators.required], | |||||
password: ['',[ | |||||
Validators.required, | |||||
Validators.minLength(6), | |||||
Validators.maxLength(40) | |||||
] | |||||
], | |||||
}); | |||||
} | |||||
get f() { | |||||
return this.loginForm.controls; | |||||
} | |||||
public onSubmit() { | |||||
if (this.loginForm.invalid) { | |||||
this.submited = true; | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
@@ -0,0 +1,63 @@ | |||||
<div class="loginWrap"> | |||||
<div class="container"> | |||||
<div class="row"> | |||||
<div class="col-md-6"></div> | |||||
<div class="col-md-6"> | |||||
<div class="loginBox"> | |||||
<h1> SIGN UP </h1> | |||||
<form action="" [formGroup]="signupForm"> | |||||
<div class="form-group"> | |||||
<label for="username">Full Name:</label> | |||||
<input type="text" class="form-control" formControlName="fullname" | |||||
[ngClass]="{ 'is-invalid': submitted && f.fullname.errors }"> | |||||
<div *ngIf="submitted && f.fullname.errors" class="invalid-feedback"> | |||||
<div *ngIf="f.fullname.errors.required">Fullname is required</div> | |||||
</div> | |||||
</div> | |||||
<div class="form-group"> | |||||
<label for="username">IMEI No:</label> | |||||
<input type="text" class="form-control" formControlName="imei" appNumberOnly | |||||
[ngClass]="{ 'is-invalid': submitted && f.imei.errors }"> | |||||
<div *ngIf="submitted && f.imei.errors" class="invalid-feedback"> | |||||
<div *ngIf="f.imei.errors.required">IMEI Number is required</div> | |||||
</div> | |||||
</div> | |||||
<div class="form-group"> | |||||
<label for="password">Password:</label> | |||||
<input type="Password" class="form-control" formControlName="password" | |||||
[ngClass]="{ 'is-invalid': submitted && f.password.errors }"> | |||||
<div *ngIf="submitted && f.password.errors" class="invalid-feedback"> | |||||
<div *ngIf="f.password.errors.required">Password is required</div> | |||||
<div *ngIf="f.password.errors.minlength"> | |||||
Password must be at least 6 characters | |||||
</div> | |||||
<div *ngIf="f.password.errors.maxlength"> | |||||
Username must not exceed 40 characters | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="form-group"> | |||||
<label for="password">Confirm Password:</label> | |||||
<input type="Password" class="form-control" formControlName="confirmPassword" | |||||
[ngClass]="{ 'is-invalid': submitted && f.confirmPassword.errors }"> | |||||
<div *ngIf="submitted && f.confirmPassword.errors" class="invalid-feedback"> | |||||
<div *ngIf="f.confirmPassword.errors.required"> | |||||
Confirm Password is required | |||||
</div> | |||||
<div *ngIf="f.confirmPassword.errors.confirmPasswordValidator"> | |||||
Confirm Password does not match | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<button class="btn btn-success " (click)="onSubmit()"> Sign up </button> | |||||
<p routerLink="/login" > Login </p> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> |
@@ -0,0 +1,2 @@ | |||||
.loginBox | |||||
margin-top: 10%!important |
@@ -0,0 +1,25 @@ | |||||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
import { SignUpComponent } from './sign-up.component'; | |||||
describe('SignUpComponent', () => { | |||||
let component: SignUpComponent; | |||||
let fixture: ComponentFixture<SignUpComponent>; | |||||
beforeEach(async () => { | |||||
await TestBed.configureTestingModule({ | |||||
declarations: [ SignUpComponent ] | |||||
}) | |||||
.compileComponents(); | |||||
}); | |||||
beforeEach(() => { | |||||
fixture = TestBed.createComponent(SignUpComponent); | |||||
component = fixture.componentInstance; | |||||
fixture.detectChanges(); | |||||
}); | |||||
it('should create', () => { | |||||
expect(component).toBeTruthy(); | |||||
}); | |||||
}); |
@@ -0,0 +1,46 @@ | |||||
import { Component, OnInit } from '@angular/core'; | |||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | |||||
import { ConfirmPasswordValidator } from 'src/app/_helper/matchpassword.validators'; | |||||
@Component({ | |||||
selector: 'app-sign-up', | |||||
templateUrl: './sign-up.component.html', | |||||
styleUrls: ['./sign-up.component.sass'], | |||||
}) | |||||
export class SignUpComponent implements OnInit { | |||||
public signupForm!: FormGroup; | |||||
public submitted: boolean = false; | |||||
constructor(private formBuilder: FormBuilder) {} | |||||
ngOnInit(): void { | |||||
this.signupForm = this.formBuilder.group( | |||||
{ | |||||
fullname: ['', Validators.required], | |||||
imei: ['', Validators.required], | |||||
email: ['', [Validators.required, Validators.email]], | |||||
password: [ | |||||
'', | |||||
[ | |||||
Validators.required, | |||||
Validators.minLength(6), | |||||
Validators.maxLength(40), | |||||
], | |||||
], | |||||
confirmPassword: ['', Validators.required], | |||||
}, | |||||
{ | |||||
validators: [ConfirmPasswordValidator('password', 'confirmPassword')], | |||||
} | |||||
); | |||||
} | |||||
get f() { | |||||
return this.signupForm.controls; | |||||
} | |||||
onSubmit(): void { | |||||
if (this.signupForm.invalid) { | |||||
this.submitted = true; | |||||
return; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
import { NgModule } from '@angular/core'; | |||||
import { RouterModule, Routes } from '@angular/router'; | |||||
import { LoginComponent } from './_shared/login/login.component'; | |||||
import { SignUpComponent } from './_shared/sign-up/sign-up.component'; | |||||
import { VeriableFormComponent } from './pages/veriable-form/veriable-form.component'; | |||||
const routes: Routes = [ | |||||
{ path: '', redirectTo: 'login', pathMatch: 'full' }, | |||||
{ path: 'login', component: LoginComponent }, | |||||
{ path: 'sign-up', component: SignUpComponent }, | |||||
{ path: 'variable-form', component: VeriableFormComponent }, | |||||
]; | |||||
@NgModule({ | |||||
imports: [RouterModule.forRoot(routes)], | |||||
exports: [RouterModule] | |||||
}) | |||||
export class AppRoutingModule { } |
@@ -0,0 +1 @@ | |||||
<router-outlet></router-outlet> |
@@ -0,0 +1,35 @@ | |||||
import { TestBed } from '@angular/core/testing'; | |||||
import { RouterTestingModule } from '@angular/router/testing'; | |||||
import { AppComponent } from './app.component'; | |||||
describe('AppComponent', () => { | |||||
beforeEach(async () => { | |||||
await TestBed.configureTestingModule({ | |||||
imports: [ | |||||
RouterTestingModule | |||||
], | |||||
declarations: [ | |||||
AppComponent | |||||
], | |||||
}).compileComponents(); | |||||
}); | |||||
it('should create the app', () => { | |||||
const fixture = TestBed.createComponent(AppComponent); | |||||
const app = fixture.componentInstance; | |||||
expect(app).toBeTruthy(); | |||||
}); | |||||
it(`should have as title 'eni'`, () => { | |||||
const fixture = TestBed.createComponent(AppComponent); | |||||
const app = fixture.componentInstance; | |||||
expect(app.title).toEqual('eni'); | |||||
}); | |||||
it('should render title', () => { | |||||
const fixture = TestBed.createComponent(AppComponent); | |||||
fixture.detectChanges(); | |||||
const compiled = fixture.nativeElement as HTMLElement; | |||||
expect(compiled.querySelector('.content span')?.textContent).toContain('eni app is running!'); | |||||
}); | |||||
}); |
@@ -0,0 +1,10 @@ | |||||
import { Component } from '@angular/core'; | |||||
@Component({ | |||||
selector: 'app-root', | |||||
templateUrl: './app.component.html', | |||||
styleUrls: ['./app.component.sass'] | |||||
}) | |||||
export class AppComponent { | |||||
title = 'eni'; | |||||
} |
@@ -0,0 +1,36 @@ | |||||
import { NgModule } from '@angular/core'; | |||||
import { BrowserModule } from '@angular/platform-browser'; | |||||
import { AppRoutingModule } from './app-routing.module'; | |||||
import { AppComponent } from './app.component'; | |||||
import { SignUpComponent } from './_shared/sign-up/sign-up.component'; | |||||
import { LoginComponent } from './_shared/login/login.component'; | |||||
import { HTTP_INTERCEPTORS } from '@angular/common/http'; | |||||
import { JwtInterceptor } from './_helper/jwt.interceptor'; | |||||
import { ErrorInterceptor } from './_helper/error.interceptor'; | |||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | |||||
import { ReactiveFormsModule } from '@angular/forms'; | |||||
import { NumberOnlyDirective } from './_helper/number-only.directive'; | |||||
import { VeriableFormComponent } from './pages/veriable-form/veriable-form.component'; | |||||
@NgModule({ | |||||
declarations: [ | |||||
AppComponent, | |||||
SignUpComponent, | |||||
LoginComponent, | |||||
NumberOnlyDirective, | |||||
VeriableFormComponent, | |||||
], | |||||
imports: [ | |||||
BrowserModule, | |||||
AppRoutingModule, | |||||
BrowserAnimationsModule, | |||||
ReactiveFormsModule, | |||||
], | |||||
providers: [ | |||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, | |||||
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }], | |||||
bootstrap: [AppComponent] | |||||
}) | |||||
export class AppModule { } |
@@ -0,0 +1,46 @@ | |||||
<div class="container"> | |||||
<div class="row"> | |||||
<div class="col-md-12"> | |||||
<div class="variableCal"> | |||||
<form [formGroup]="formGroup"> | |||||
<div class="form-group" formArrayName="items" *ngFor="let item of items.controls; let i = index"> | |||||
<div class="row" [formGroupName]="i"> | |||||
<div class="col-md-3"> | |||||
<select formControlName="type" (change)="onTypeChange(i)" class="form-control"> | |||||
<option value="" selected disabled > Select </option> | |||||
<option value="variable">Variable</option> | |||||
<option value="text">Text</option> | |||||
</select> | |||||
</div> | |||||
<div class="col-md-3" *ngIf="item.get('type')?.value === 'variable'"> | |||||
<select formControlName="variable" class="form-control"> | |||||
<option value="" selected disabled > Select </option> | |||||
<option *ngFor="let v of variableList" [value]="v">{{ v }}</option> | |||||
</select> | |||||
<div> | |||||
<p>Variable Calculation: {{ calculateVariable(i) }}</p> | |||||
</div> | |||||
</div> | |||||
<div *ngIf="item.get('type')?.value === 'text'" class="col-md-3"> | |||||
<input formControlName="text" placeholder="Enter text here" class="form-control" /> | |||||
</div> | |||||
<div class="col-md-3"> | |||||
<button class="btn btn-danger" (click)="removeRow(i)" *ngIf="items.length > 1">Remove Row</button> | |||||
<button class="btn btn-primary ml-2" (click)="addRow()" *ngIf="i === items.length - 1">Add Row</button> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="row"> | |||||
<div class="col-md-12"> | |||||
<button type="submit" class="btn btn-primary" (click)="onSubmit()" >Submit</button> | |||||
</div> | |||||
</div> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> |
@@ -0,0 +1,25 @@ | |||||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||||
import { VeriableFormComponent } from './veriable-form.component'; | |||||
describe('VeriableFormComponent', () => { | |||||
let component: VeriableFormComponent; | |||||
let fixture: ComponentFixture<VeriableFormComponent>; | |||||
beforeEach(async () => { | |||||
await TestBed.configureTestingModule({ | |||||
declarations: [ VeriableFormComponent ] | |||||
}) | |||||
.compileComponents(); | |||||
}); | |||||
beforeEach(() => { | |||||
fixture = TestBed.createComponent(VeriableFormComponent); | |||||
component = fixture.componentInstance; | |||||
fixture.detectChanges(); | |||||
}); | |||||
it('should create', () => { | |||||
expect(component).toBeTruthy(); | |||||
}); | |||||
}); |
@@ -0,0 +1,63 @@ | |||||
import { Component, OnInit } from '@angular/core'; | |||||
import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; | |||||
@Component({ | |||||
selector: 'app-veriable-form', | |||||
templateUrl: './veriable-form.component.html', | |||||
styleUrls: ['./veriable-form.component.sass'] | |||||
}) | |||||
export class VeriableFormComponent implements OnInit { | |||||
formGroup: FormGroup; | |||||
variableList = ['Variable1', 'Variable2', 'Variable3']; // Example variable list | |||||
constructor(private fb: FormBuilder) { | |||||
this.formGroup = this.fb.group({ | |||||
items: this.fb.array([this.createItem()]) | |||||
}); | |||||
} | |||||
ngOnInit(): void { | |||||
} | |||||
get items() { | |||||
return this.formGroup.get('items') as FormArray; | |||||
} | |||||
createItem(): FormGroup { | |||||
return this.fb.group({ | |||||
type: [''], | |||||
variable: [''], | |||||
text: [''] | |||||
}); | |||||
} | |||||
addRow() { | |||||
this.items.push(this.createItem()); | |||||
} | |||||
removeRow(index: number) { | |||||
this.items.removeAt(index); | |||||
} | |||||
onTypeChange(index: number) { | |||||
const type = this.items?.at(index)?.get('type')?.value; | |||||
if (type === 'variable') { | |||||
this.items?.at(index)?.get('text')?.setValue(''); | |||||
} else { | |||||
this.items?.at(index)?.get('variable')?.setValue(''); | |||||
} | |||||
} | |||||
calculateVariable(index: number): string { | |||||
const selectedVariable = this.items?.at(index)?.get('variable')?.value; | |||||
return `Calculated value for ${selectedVariable}`; | |||||
} | |||||
onSubmit() { | |||||
const formData = this.formGroup.value; | |||||
console.log(formData); | |||||
} | |||||
} |
@@ -0,0 +1,4 @@ | |||||
export const environment = { | |||||
production: true, | |||||
apiUrl: '' | |||||
}; |
@@ -0,0 +1,17 @@ | |||||
// This file can be replaced during build by using the `fileReplacements` array. | |||||
// `ng build` replaces `environment.ts` with `environment.prod.ts`. | |||||
// The list of file replacements can be found in `angular.json`. | |||||
export const environment = { | |||||
production: false, | |||||
apiUrl: '' | |||||
}; | |||||
/* | |||||
* For easier debugging in development mode, you can import the following file | |||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. | |||||
* | |||||
* This import should be commented out in production mode because it will have a negative impact | |||||
* on performance if an error is thrown. | |||||
*/ | |||||
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. |
@@ -0,0 +1,17 @@ | |||||
<!doctype html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<title>Eni</title> | |||||
<base href="/"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<link rel="icon" type="image/x-icon" href="favicon.ico"> | |||||
<link rel="preconnect" href="https://fonts.gstatic.com"> | |||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet"> | |||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | |||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous"> | |||||
</head> | |||||
<body class="mat-typography"> | |||||
<app-root></app-root> | |||||
</body> | |||||
</html> |
@@ -0,0 +1,12 @@ | |||||
import { enableProdMode } from '@angular/core'; | |||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; | |||||
import { AppModule } from './app/app.module'; | |||||
import { environment } from './environments/environment'; | |||||
if (environment.production) { | |||||
enableProdMode(); | |||||
} | |||||
platformBrowserDynamic().bootstrapModule(AppModule) | |||||
.catch(err => console.error(err)); |
@@ -0,0 +1,65 @@ | |||||
/** | |||||
* This file includes polyfills needed by Angular and is loaded before the app. | |||||
* You can add your own extra polyfills to this file. | |||||
* | |||||
* This file is divided into 2 sections: | |||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. | |||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main | |||||
* file. | |||||
* | |||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that | |||||
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), | |||||
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. | |||||
* | |||||
* Learn more in https://angular.io/guide/browser-support | |||||
*/ | |||||
/*************************************************************************************************** | |||||
* BROWSER POLYFILLS | |||||
*/ | |||||
/** | |||||
* IE11 requires the following for NgClass support on SVG elements | |||||
*/ | |||||
// import 'classlist.js'; // Run `npm install --save classlist.js`. | |||||
/** | |||||
* Web Animations `@angular/platform-browser/animations` | |||||
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. | |||||
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). | |||||
*/ | |||||
// import 'web-animations-js'; // Run `npm install --save web-animations-js`. | |||||
/** | |||||
* By default, zone.js will patch all possible macroTask and DomEvents | |||||
* user can disable parts of macroTask/DomEvents patch by setting following flags | |||||
* because those flags need to be set before `zone.js` being loaded, and webpack | |||||
* will put import in the top of bundle, so user need to create a separate file | |||||
* in this directory (for example: zone-flags.ts), and put the following flags | |||||
* into that file, and then add the following code before importing zone.js. | |||||
* import './zone-flags'; | |||||
* | |||||
* The flags allowed in zone-flags.ts are listed here. | |||||
* | |||||
* The following flags will work for all browsers. | |||||
* | |||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame | |||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick | |||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames | |||||
* | |||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js | |||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge | |||||
* | |||||
* (window as any).__Zone_enable_cross_context_check = true; | |||||
* | |||||
*/ | |||||
/*************************************************************************************************** | |||||
* Zone JS is required by default for Angular itself. | |||||
*/ | |||||
import 'zone.js'; // Included with Angular CLI. | |||||
/*************************************************************************************************** | |||||
* APPLICATION IMPORTS | |||||
*/ |
@@ -0,0 +1,54 @@ | |||||
@import "~@angular/material/prebuilt-themes/indigo-pink.css" | |||||
.loginWrap | |||||
background: #ccc | |||||
min-height:100vh | |||||
.loginBox | |||||
width: 80% | |||||
margin: auto | |||||
background: #fff | |||||
padding: 20px | |||||
border-radius: 10px | |||||
margin-top: 25% | |||||
.loginBox h1 | |||||
text-align: center | |||||
font-size: 28px | |||||
font-weight: 600 | |||||
letter-spacing: 5px | |||||
color: #08c | |||||
text-decoration: underline | |||||
padding: 5px | |||||
text-transform: uppercase | |||||
.loginBox button | |||||
width: 200px | |||||
font-weight: 500 | |||||
font-size: 14px | |||||
border: none | |||||
border-radius: 10px | |||||
margin: 1rem auto | |||||
display: block | |||||
.loginBox p | |||||
text-align: right | |||||
font-size: 14px | |||||
font-weight: 400 | |||||
letter-spacing: 1px | |||||
color: #08c | |||||
text-decoration: underline | |||||
cursor: pointer | |||||
text-transform: uppercase | |||||
.loginBox input | |||||
background: #fff | |||||
box-shaddow: none | |||||
border: 1px solid #cccccc | |||||
border-radius:10px | |||||
font-size: 14px | |||||
label | |||||
font-weight:500 | |||||
font-family: 'Roboto' |
@@ -0,0 +1,27 @@ | |||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files | |||||
import 'zone.js/testing'; | |||||
import { getTestBed } from '@angular/core/testing'; | |||||
import { | |||||
BrowserDynamicTestingModule, | |||||
platformBrowserDynamicTesting | |||||
} from '@angular/platform-browser-dynamic/testing'; | |||||
declare const require: { | |||||
context(path: string, deep?: boolean, filter?: RegExp): { | |||||
keys(): string[]; | |||||
<T>(id: string): T; | |||||
}; | |||||
}; | |||||
// First, initialize the Angular testing environment. | |||||
getTestBed().initTestEnvironment( | |||||
BrowserDynamicTestingModule, | |||||
platformBrowserDynamicTesting(), | |||||
{ teardown: { destroyAfterEach: true }}, | |||||
); | |||||
// Then we find all the tests. | |||||
const context = require.context('./', true, /\.spec\.ts$/); | |||||
// And load the modules. | |||||
context.keys().map(context); |
@@ -0,0 +1,15 @@ | |||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ | |||||
{ | |||||
"extends": "./tsconfig.json", | |||||
"compilerOptions": { | |||||
"outDir": "./out-tsc/app", | |||||
"types": [] | |||||
}, | |||||
"files": [ | |||||
"src/main.ts", | |||||
"src/polyfills.ts" | |||||
], | |||||
"include": [ | |||||
"src/**/*.d.ts" | |||||
] | |||||
} |
@@ -0,0 +1,30 @@ | |||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ | |||||
{ | |||||
"compileOnSave": false, | |||||
"compilerOptions": { | |||||
"baseUrl": "./", | |||||
"outDir": "./dist/out-tsc", | |||||
"forceConsistentCasingInFileNames": true, | |||||
"strict": true, | |||||
"noImplicitReturns": true, | |||||
"noFallthroughCasesInSwitch": true, | |||||
"sourceMap": true, | |||||
"declaration": false, | |||||
"downlevelIteration": true, | |||||
"experimentalDecorators": true, | |||||
"moduleResolution": "node", | |||||
"importHelpers": true, | |||||
"target": "es2017", | |||||
"module": "es2020", | |||||
"lib": [ | |||||
"es2018", | |||||
"dom" | |||||
] | |||||
}, | |||||
"angularCompilerOptions": { | |||||
"enableI18nLegacyMessageIdFormat": false, | |||||
"strictInjectionParameters": true, | |||||
"strictInputAccessModifiers": true, | |||||
"strictTemplates": true | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ | |||||
{ | |||||
"extends": "./tsconfig.json", | |||||
"compilerOptions": { | |||||
"outDir": "./out-tsc/spec", | |||||
"types": [ | |||||
"jasmine" | |||||
] | |||||
}, | |||||
"files": [ | |||||
"src/test.ts", | |||||
"src/polyfills.ts" | |||||
], | |||||
"include": [ | |||||
"src/**/*.spec.ts", | |||||
"src/**/*.d.ts" | |||||
] | |||||
} |