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