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