add angular18 boilerplate
							
								
								
									
										9
									
								
								angular18/src/app/app.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,9 @@ | |||
| <div class="scrollable-container"> | ||||
|   <div class="layout"> | ||||
| 
 | ||||
|     <router-outlet></router-outlet> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
| <app-toast aria-live="polite" aria-atomic="true" *ngIf="!storeService.isServer()"></app-toast> | ||||
							
								
								
									
										9
									
								
								angular18/src/app/app.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,9 @@ | |||
| .scrollable-container { | ||||
|   height: 100vh; | ||||
|   display: block; | ||||
|   .layout { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     min-height: 100vh; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										56
									
								
								angular18/src/app/app.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,56 @@ | |||
| // Angular modules
 | ||||
| import { NgIf }             from '@angular/common'; | ||||
| import { Component }        from '@angular/core'; | ||||
| import { OnInit }           from '@angular/core'; | ||||
| import { RouterOutlet }     from '@angular/router'; | ||||
| 
 | ||||
| // Services
 | ||||
| import { StoreService }     from '@services/store.service'; | ||||
| 
 | ||||
| // Components
 | ||||
| import { ToastComponent }   from '@blocks/toast/toast.component'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-root', | ||||
|   templateUrl : './app.component.html', | ||||
|   styleUrls   : ['./app.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [RouterOutlet, ToastComponent, NgIf] | ||||
| }) | ||||
| export class AppComponent implements OnInit | ||||
| { | ||||
|   constructor | ||||
|   ( | ||||
|     public storeService : StoreService, | ||||
|   ) | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Computed props -----------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Subscriptions ------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| } | ||||
							
								
								
									
										84
									
								
								angular18/src/app/app.config.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,84 @@ | |||
| // Angular modules
 | ||||
| import { DatePipe }               from '@angular/common'; | ||||
| import { withFetch }              from '@angular/common/http'; | ||||
| import { withInterceptorsFromDi } from '@angular/common/http'; | ||||
| import { provideHttpClient }      from '@angular/common/http'; | ||||
| import { HttpClient }             from '@angular/common/http'; | ||||
| import { ApplicationConfig }      from '@angular/core'; | ||||
| import { importProvidersFrom }    from '@angular/core'; | ||||
| import { BrowserModule }          from '@angular/platform-browser'; | ||||
| import { provideClientHydration } from '@angular/platform-browser'; | ||||
| import { provideAnimations }      from '@angular/platform-browser/animations'; | ||||
| import { provideRouter }          from '@angular/router'; | ||||
| import { withInMemoryScrolling }  from '@angular/router'; | ||||
| import { withRouterConfig }       from '@angular/router'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateModule }        from '@ngx-translate/core'; | ||||
| import { TranslateLoader }        from '@ngx-translate/core'; | ||||
| import { TranslateHttpLoader }    from '@ngx-translate/http-loader'; | ||||
| import { AngularSvgIconModule }   from 'angular-svg-icon'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }            from '@env/environment'; | ||||
| import { routes }                 from './app.routes'; | ||||
| 
 | ||||
| // Services
 | ||||
| import { AppService }             from '@services/app.service'; | ||||
| import { StoreService }           from '@services/store.service'; | ||||
| 
 | ||||
| export function createTranslateLoader(http : HttpClient) | ||||
| { | ||||
|   return new TranslateHttpLoader(http, './assets/i18n/', '.json'); | ||||
| } | ||||
| 
 | ||||
| export const appConfig : ApplicationConfig = { | ||||
|   providers : [ | ||||
| 
 | ||||
|     // Routing
 | ||||
|     provideRouter( | ||||
|       routes, | ||||
|       withRouterConfig({ | ||||
|         onSameUrlNavigation : 'reload', | ||||
|       }), | ||||
|       withInMemoryScrolling({ | ||||
|         scrollPositionRestoration : 'enabled' | ||||
|       }), | ||||
|     ), | ||||
| 
 | ||||
|     importProvidersFrom( | ||||
|       // Angular modules
 | ||||
|       BrowserModule, | ||||
| 
 | ||||
|       // External modules
 | ||||
|       TranslateModule.forRoot({ | ||||
|         defaultLanguage : environment.defaultLanguage, | ||||
|         loader : { | ||||
|           provide    : TranslateLoader, | ||||
|           useFactory : (createTranslateLoader), | ||||
|           deps       : [HttpClient] | ||||
|         } | ||||
|       }), | ||||
|       AngularSvgIconModule.forRoot(), | ||||
| 
 | ||||
|       // Internal modules
 | ||||
|     ), | ||||
| 
 | ||||
|     // External modules
 | ||||
| 
 | ||||
|     // Services
 | ||||
|     StoreService, | ||||
|     AppService, | ||||
| 
 | ||||
|     // Pipes
 | ||||
|     DatePipe, | ||||
| 
 | ||||
|     // Guards
 | ||||
| 
 | ||||
|     // Resolvers
 | ||||
| 
 | ||||
|     provideHttpClient(withFetch(), withInterceptorsFromDi()), | ||||
|     provideAnimations(), | ||||
|     provideClientHydration(), | ||||
|   ] | ||||
| }; | ||||
							
								
								
									
										18
									
								
								angular18/src/app/app.routes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,18 @@ | |||
| // Angular modules
 | ||||
| import { Routes } from '@angular/router'; | ||||
| 
 | ||||
| export const routes : Routes = [ | ||||
|   { | ||||
|     path         : 'auth', | ||||
|     loadChildren : () => import('./pages/auth/auth.routes').then(m => m.routes), | ||||
|   }, | ||||
|   { | ||||
|     path          : 'home', | ||||
|     loadComponent : () => import('./pages/home/home.component').then(m => m.HomeComponent), | ||||
|   }, | ||||
|   { path : '', redirectTo : '/home', pathMatch : 'full' }, | ||||
|   { | ||||
|     path          : '**', | ||||
|     loadComponent : () => import('./pages/not-found/not-found.component').then(m => m.NotFoundComponent), | ||||
|   }, | ||||
| ]; | ||||
							
								
								
									
										21
									
								
								angular18/src/app/pages/auth/auth.component-2.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,21 @@ | |||
| <!-- NOTE Header with Project logo --> | ||||
| <header class="position-fixed w-100 text-center py-2 bg-light"> | ||||
|   <img src="./assets/img/project/logo.svg" [alt]="appName + ' Logo'" id="auth-svg" class="img-fluid"/> | ||||
| </header> | ||||
| 
 | ||||
| <!-- NOTE Loading --> | ||||
| <app-progress-bar *ngIf="storeService.isLoading$ | async" class="position-fixed w-100"></app-progress-bar> | ||||
| 
 | ||||
| <div class="auth-component-container"> | ||||
|   <div class="auth-component"> | ||||
| 
 | ||||
|     <!-- NOTE Form --> | ||||
|     <router-outlet></router-outlet> | ||||
| 
 | ||||
|   </div> | ||||
| </div> | ||||
| 
 | ||||
| <!-- NOTE Footer with Application version --> | ||||
| <footer class="position-fixed w-100 text-white text-end px-2 d-flex align-items-center justify-content-end bg-primary"> | ||||
|   <div class="small">{{ 'VERSION' | translate }} {{ appVersion }}</div> | ||||
| </footer> | ||||
							
								
								
									
										41
									
								
								angular18/src/app/pages/auth/auth.component-2.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,41 @@ | |||
| // NOTE Header / Loading / Footer | ||||
| 
 | ||||
| header, app-progress-bar, footer { | ||||
|   z-index: 10; | ||||
| } | ||||
| 
 | ||||
| header { | ||||
|   top: 0; | ||||
|   height: 56px; | ||||
| } | ||||
| 
 | ||||
| app-progress-bar { | ||||
|   top: 56px; | ||||
| } | ||||
| 
 | ||||
| footer { | ||||
|   bottom: 0; | ||||
|   height: 40px; | ||||
| } | ||||
| 
 | ||||
| // NOTE Layout | ||||
| 
 | ||||
| .auth-component-container { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   min-height: 100vh; | ||||
|   padding-top: 60px; | ||||
|   padding-bottom: 40px; | ||||
|   .auth-component { | ||||
|     width: 100%; | ||||
|     max-width: 350px; | ||||
|     padding: 15px; | ||||
|     margin: auto; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // NOTE Images | ||||
| 
 | ||||
| #auth-svg { | ||||
|   height: 40px; | ||||
| } | ||||
							
								
								
									
										10
									
								
								angular18/src/app/pages/auth/auth.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,10 @@ | |||
| <app-progress-bar class="position-fixed w-100" *ngIf="storeService.isLoading$ | async"></app-progress-bar> | ||||
| <div class="row g-0"> | ||||
|   <div class="col img-wrapper d-none d-md-flex"> | ||||
|     <img [src]="'assets/img/project/login.png'" alt="Login image"> | ||||
|   </div> | ||||
|   <div class="col d-flex flex-column justify-content-center p-3"> | ||||
|     <!-- NOTE Content --> | ||||
|     <router-outlet></router-outlet> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										14
									
								
								angular18/src/app/pages/auth/auth.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,14 @@ | |||
| // NOTE Layout | ||||
| .row { | ||||
|   min-height: 100vh; | ||||
|   .col.img-wrapper { | ||||
|     min-height: 100vh; | ||||
|     overflow: hidden; | ||||
|     img { | ||||
|       object-fit: cover; | ||||
|       width: 100%; | ||||
|       height: 100%; | ||||
|       position: relative; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										66
									
								
								angular18/src/app/pages/auth/auth.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,66 @@ | |||
| // Angular modules
 | ||||
| import { NgIf }                 from '@angular/common'; | ||||
| import { AsyncPipe }            from '@angular/common'; | ||||
| import { Component }            from '@angular/core'; | ||||
| import { OnInit }               from '@angular/core'; | ||||
| import { RouterOutlet }         from '@angular/router'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }          from '@env/environment'; | ||||
| 
 | ||||
| // Services
 | ||||
| import { StoreService }         from '@services/store.service'; | ||||
| 
 | ||||
| // Components
 | ||||
| import { ProgressBarComponent } from '@blocks/progress-bar/progress-bar.component'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-auth', | ||||
|   templateUrl : './auth.component.html', | ||||
|   styleUrls   : ['./auth.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [NgIf, ProgressBarComponent, RouterOutlet, AsyncPipe] | ||||
| }) | ||||
| export class AuthComponent implements OnInit | ||||
| { | ||||
|   // NOTE Component properties
 | ||||
|   public appName    : string  = environment.appName; | ||||
|   public appVersion : string  = environment.version; | ||||
| 
 | ||||
|   constructor | ||||
|   ( | ||||
|     public storeService : StoreService, | ||||
|   ) | ||||
|   { | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Computed props -----------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Subscriptions ------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										27
									
								
								angular18/src/app/pages/auth/auth.routes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,27 @@ | |||
| // Angular modules
 | ||||
| import { Routes } from '@angular/router'; | ||||
| 
 | ||||
| export const routes : Routes = [ | ||||
|   { | ||||
|     path     : '', | ||||
|     children : [ | ||||
|       { | ||||
|         path       : '', | ||||
|         redirectTo : 'login', | ||||
|         pathMatch  : 'full', | ||||
|       }, | ||||
|       { | ||||
|         path          : 'login', | ||||
|         loadComponent : () => import('./login/login.component').then(m => m.LoginComponent), | ||||
|       }, | ||||
|       { | ||||
|         path          : 'forgot-password', | ||||
|         loadComponent : () => import('./forgot-password/forgot-password.component').then(m => m.ForgotPasswordComponent), | ||||
|       }, | ||||
|       { | ||||
|         path          : 'validate-account', | ||||
|         loadComponent : () => import('./validate-account/validate-account.component').then(m => m.ValidateAccountComponent), | ||||
|       }, | ||||
|     ] | ||||
|   } | ||||
| ]; | ||||
|  | @ -0,0 +1,38 @@ | |||
| <div class="text-center text-primary mb-4"> | ||||
|   <div class="h2 fw-normal mb-0">{{ 'FORGOT_YOUR_PWD' | translate }}</div> | ||||
| </div> | ||||
| 
 | ||||
| <div class="d-flex justify-content-center"> | ||||
|   <div class="wrapper"> | ||||
| 
 | ||||
|     <!-- NOTE Login form --> | ||||
|     <form [formGroup]="formGroup" (ngSubmit)="onClickSubmit()"> | ||||
| 
 | ||||
|       <!-- NOTE Email --> | ||||
|       <div class="mb-3"> | ||||
|         <label for="email" class="form-label text-start w-100">{{ 'EMAIL' | translate }}</label> | ||||
|         <input type="email" class="form-control" id="email" | ||||
|           formControlName="email" | ||||
|           [ngClass]="{ 'is-invalid' : formGroup.controls.email.errors && formGroup.controls.email.touched }"> | ||||
|         <!-- NOTE Errors --> | ||||
|         <div class="invalid-feedback" *ngIf="formGroup.controls.email.hasError('required')"> | ||||
|           {{ 'FIELD_REQUIRED' | translate }} | ||||
|         </div> | ||||
|         <div class="invalid-feedback" *ngIf="formGroup.controls.email.hasError('email')"> | ||||
|           {{ 'FIELD_EMAIL' | translate }} | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="text-center"> | ||||
|         <button type="submit" class="btn btn-primary px-5" [disabled]="!formGroup.valid"> | ||||
|           {{ 'RESET_PASSWORD' | translate }} | ||||
|         </button> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="text-center my-3"> | ||||
|         <a routerLink="/auth/login">{{ 'BACK_TO_LOGIN' | translate }}</a> | ||||
|       </div> | ||||
| 
 | ||||
|     </form> | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,4 @@ | |||
| .wrapper { | ||||
|   max-width: 350px; | ||||
|   width: 100%; | ||||
| } | ||||
|  | @ -0,0 +1,89 @@ | |||
| // Angular modules
 | ||||
| import { NgClass }             from '@angular/common'; | ||||
| import { NgIf }                from '@angular/common'; | ||||
| import { Component }           from '@angular/core'; | ||||
| import { FormGroup }           from '@angular/forms'; | ||||
| import { FormsModule }         from '@angular/forms'; | ||||
| import { ReactiveFormsModule } from '@angular/forms'; | ||||
| import { FormControl }         from '@angular/forms'; | ||||
| import { Validators }          from '@angular/forms'; | ||||
| import { Router }              from '@angular/router'; | ||||
| import { RouterLink }          from '@angular/router'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateModule }     from '@ngx-translate/core'; | ||||
| 
 | ||||
| // Services
 | ||||
| import { AppService }          from '@services/app.service'; | ||||
| import { StoreService }        from '@services/store.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-forgot-password', | ||||
|   templateUrl : './forgot-password.component.html', | ||||
|   styleUrls   : ['./forgot-password.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [FormsModule, ReactiveFormsModule, NgClass, NgIf, RouterLink, TranslateModule] | ||||
| }) | ||||
| export class ForgotPasswordComponent | ||||
| { | ||||
|   public formGroup !: FormGroup<{ | ||||
|     email : FormControl<string> | ||||
|   }>; | ||||
| 
 | ||||
|   constructor | ||||
|   ( | ||||
|     public  router       : Router, | ||||
|     private storeService : StoreService, | ||||
|     private appService   : AppService, | ||||
|   ) | ||||
|   { | ||||
|     this.initFormGroup(); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private initFormGroup() : void | ||||
|   { | ||||
|     this.formGroup = new FormGroup({ | ||||
|       email      : new FormControl<string>({ | ||||
|         value    : '', | ||||
|         disabled : false | ||||
|       }, { validators : [Validators.required, Validators.email], nonNullable : true }), | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public async onClickSubmit() : Promise<void> | ||||
|   { | ||||
|     await this.forgotPassword(); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private async forgotPassword() : Promise<void> | ||||
|   { | ||||
|     this.storeService.isLoading.set(true); | ||||
| 
 | ||||
|     const email   = this.formGroup.controls.email.getRawValue(); | ||||
|     const success = await this.appService.forgotPassword(email); | ||||
| 
 | ||||
|     this.storeService.isLoading.set(false); | ||||
| 
 | ||||
|     if (!success) | ||||
|       return; | ||||
| 
 | ||||
|     // NOTE Redirect to validate account
 | ||||
|     this.router.navigate(['/auth/validate-account']); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| } | ||||
							
								
								
									
										52
									
								
								angular18/src/app/pages/auth/login/login.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,52 @@ | |||
| <div class="text-center text-primary mb-4"> | ||||
|   <div class="h2 fw-normal mb-0">{{ 'WELCOME_TO' | translate }}</div> | ||||
|   <div class="h1 fw-bold text-uppercase">{{ appName }}</div> | ||||
|   <div class="fw-normal text-black">{{ 'PROJECT_DESC' | translate }}</div> | ||||
| </div> | ||||
| 
 | ||||
| <div class="d-flex justify-content-center"> | ||||
|   <div class="wrapper"> | ||||
| 
 | ||||
|     <!-- NOTE Login form --> | ||||
|     <form [formGroup]="formGroup" (ngSubmit)="onClickSubmit()"> | ||||
| 
 | ||||
|       <!-- NOTE Email --> | ||||
|       <div class="mb-3"> | ||||
|         <label for="email" class="form-label text-start w-100">{{ 'EMAIL' | translate }}</label> | ||||
|         <input type="email" class="form-control" id="email" | ||||
|           formControlName="email" | ||||
|           [ngClass]="{ 'is-invalid' : formGroup.controls.email.errors && formGroup.controls.email.touched }"> | ||||
|         <!-- NOTE Errors --> | ||||
|         <div class="invalid-feedback" *ngIf="formGroup.controls.email.hasError('required')"> | ||||
|           {{ 'FIELD_REQUIRED' | translate }} | ||||
|         </div> | ||||
|         <div class="invalid-feedback" *ngIf="formGroup.controls.email.hasError('email')"> | ||||
|           {{ 'FIELD_EMAIL' | translate }} | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <!-- NOTE Password --> | ||||
|       <div class="mb-3"> | ||||
|         <label for="password" class="form-label text-start w-100">{{ 'PASSWORD' | translate }}</label> | ||||
|         <input type="password" class="form-control" id="password" | ||||
|           formControlName="password" | ||||
|           [ngClass]="{ 'is-invalid' : formGroup.controls.password.errors && formGroup.controls.password.touched }"> | ||||
|         <!-- NOTE Errors --> | ||||
|         <div class="invalid-feedback" *ngIf="formGroup.controls.password.hasError('required')"> | ||||
|           {{ 'FIELD_REQUIRED' | translate }} | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="text-end mb-3"> | ||||
|         <a routerLink="/auth/forgot-password" class="">{{ 'FORGOT_YOUR_PWD' | translate }}</a> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="text-center"> | ||||
|         <button type="submit" class="btn btn-primary mb-3 px-5" [disabled]="!formGroup.valid"> | ||||
|           <span class="px-4 d-block">{{ 'SIGN_IN' | translate }}</span> | ||||
|         </button> | ||||
|       </div> | ||||
| 
 | ||||
|     </form> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										4
									
								
								angular18/src/app/pages/auth/login/login.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,4 @@ | |||
| .wrapper { | ||||
|   max-width: 350px; | ||||
|   width: 100%; | ||||
| } | ||||
							
								
								
									
										100
									
								
								angular18/src/app/pages/auth/login/login.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,100 @@ | |||
| // Angular modules
 | ||||
| import { NgClass }             from '@angular/common'; | ||||
| import { NgIf }                from '@angular/common'; | ||||
| import { Component }           from '@angular/core'; | ||||
| import { FormGroup }           from '@angular/forms'; | ||||
| import { FormsModule }         from '@angular/forms'; | ||||
| import { ReactiveFormsModule } from '@angular/forms'; | ||||
| import { FormControl }         from '@angular/forms'; | ||||
| import { Validators }          from '@angular/forms'; | ||||
| import { Router }              from '@angular/router'; | ||||
| import { RouterLink }          from '@angular/router'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateModule }     from '@ngx-translate/core'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }         from '@env/environment'; | ||||
| 
 | ||||
| // Services
 | ||||
| import { AppService }          from '@services/app.service'; | ||||
| import { StoreService }        from '@services/store.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-login', | ||||
|   templateUrl : './login.component.html', | ||||
|   styleUrls   : ['./login.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [FormsModule, ReactiveFormsModule, NgClass, NgIf, RouterLink, TranslateModule] | ||||
| }) | ||||
| export class LoginComponent | ||||
| { | ||||
|   public appName : string = environment.appName; | ||||
|   public formGroup !: FormGroup<{ | ||||
|     email    : FormControl<string>, | ||||
|     password : FormControl<string>, | ||||
|   }>; | ||||
| 
 | ||||
|   constructor | ||||
|   ( | ||||
|     private router       : Router, | ||||
|     private storeService : StoreService, | ||||
|     private appService   : AppService, | ||||
|   ) | ||||
|   { | ||||
|     this.initFormGroup(); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private initFormGroup() : void | ||||
|   { | ||||
|     this.formGroup = new FormGroup({ | ||||
|       email      : new FormControl<string>({ | ||||
|         value    : '', | ||||
|         disabled : false | ||||
|       }, { validators : [Validators.required, Validators.email], nonNullable : true }), | ||||
|       password   : new FormControl<string>({ | ||||
|         value    : '', | ||||
|         disabled : false | ||||
|       }, { validators : [Validators.required], nonNullable : true }) | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public async onClickSubmit() : Promise<void> | ||||
|   { | ||||
|     await this.authenticate(); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private async authenticate() : Promise<void> | ||||
|   { | ||||
|     this.storeService.isLoading.set(true); | ||||
| 
 | ||||
|     const email    = this.formGroup.controls.email.getRawValue(); | ||||
|     const password = this.formGroup.controls.password.getRawValue(); | ||||
|     const success  = await this.appService.authenticate(email, password); | ||||
| 
 | ||||
|     this.storeService.isLoading.set(false); | ||||
| 
 | ||||
|     if (!success) | ||||
|       return; | ||||
| 
 | ||||
|     // NOTE Redirect to home
 | ||||
|     this.router.navigate(['/home']); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,31 @@ | |||
| <div class="text-center text-primary mb-4"> | ||||
|   <div class="h2 fw-normal mb-0">{{ 'VALIDATE_ACCOUNT' | translate }}</div> | ||||
| </div> | ||||
| 
 | ||||
| <div class="d-flex justify-content-center"> | ||||
|   <div class="wrapper"> | ||||
| 
 | ||||
|     <!-- NOTE Login form --> | ||||
|     <form [formGroup]="formGroup" (ngSubmit)="onClickSubmit()"> | ||||
| 
 | ||||
|       <!-- NOTE Password --> | ||||
|       <div class="mb-3"> | ||||
|         <label for="password" class="form-label text-start w-100">{{ 'PASSWORD' | translate }}</label> | ||||
|         <input type="password" class="form-control" id="password" | ||||
|           formControlName="password" | ||||
|           [ngClass]="{ 'is-invalid' : formGroup.controls.password.errors && formGroup.controls.password.touched }"> | ||||
|         <!-- NOTE Errors --> | ||||
|         <div class="invalid-feedback" *ngIf="formGroup.controls.password.hasError('required')"> | ||||
|           {{ 'FIELD_REQUIRED' | translate }} | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="text-center"> | ||||
|         <button type="submit" class="btn btn-primary px-5" [disabled]="!formGroup.valid"> | ||||
|           {{ 'VALIDATE' | translate }} | ||||
|         </button> | ||||
|       </div> | ||||
| 
 | ||||
|     </form> | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,4 @@ | |||
| .wrapper { | ||||
|   max-width: 350px; | ||||
|   width: 100%; | ||||
| } | ||||
|  | @ -0,0 +1,111 @@ | |||
| // Angular modules
 | ||||
| import { NgClass }             from '@angular/common'; | ||||
| import { NgIf }                from '@angular/common'; | ||||
| import { OnInit }              from '@angular/core'; | ||||
| import { Component }           from '@angular/core'; | ||||
| import { FormGroup }           from '@angular/forms'; | ||||
| import { FormsModule }         from '@angular/forms'; | ||||
| import { ReactiveFormsModule } from '@angular/forms'; | ||||
| import { FormControl }         from '@angular/forms'; | ||||
| import { Validators }          from '@angular/forms'; | ||||
| import { ActivatedRoute }      from '@angular/router'; | ||||
| import { Router }              from '@angular/router'; | ||||
| import { Params }              from '@angular/router'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateModule }     from '@ngx-translate/core'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }         from '@env/environment'; | ||||
| 
 | ||||
| // Services
 | ||||
| import { AppService }          from '@services/app.service'; | ||||
| import { StoreService }        from '@services/store.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-validate-account', | ||||
|   templateUrl : './validate-account.component.html', | ||||
|   styleUrls   : ['./validate-account.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [FormsModule, ReactiveFormsModule, NgClass, NgIf, TranslateModule] | ||||
| }) | ||||
| export class ValidateAccountComponent implements OnInit | ||||
| { | ||||
|   public  formGroup    !: FormGroup<{ | ||||
|     password : FormControl<string> | ||||
|   }>; | ||||
|   private tokenFromUrl  : string = ''; | ||||
| 
 | ||||
|   constructor | ||||
|   ( | ||||
|     private router         : Router, | ||||
|     private storeService   : StoreService, | ||||
|     private activatedRoute : ActivatedRoute, | ||||
|     private appService     : AppService, | ||||
|   ) | ||||
|   { | ||||
|     this.initFormGroup(); | ||||
|   } | ||||
| 
 | ||||
|   public async ngOnInit() : Promise<void> | ||||
|   { | ||||
|     // NOTE Get token from URL
 | ||||
|     this.activatedRoute.queryParams.subscribe((params : Params) => | ||||
|     { | ||||
|       this.tokenFromUrl = params['token']; | ||||
|       if (!environment.production) | ||||
|         console.log('ValidateAccountComponent : ngOnInit -> Token : ', this.tokenFromUrl); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private initFormGroup() : void | ||||
|   { | ||||
|     this.formGroup = new FormGroup({ | ||||
|       password   : new FormControl<string>({ | ||||
|         value    : '', | ||||
|         disabled : false | ||||
|       }, { validators : [Validators.required], nonNullable : true }), | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public async onClickSubmit() : Promise<void> | ||||
|   { | ||||
|     if (!this.tokenFromUrl) | ||||
|       return; | ||||
| 
 | ||||
|     await this.validateNewAccount(); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private async validateNewAccount() : Promise<void> | ||||
|   { | ||||
|     this.storeService.isLoading.set(true); | ||||
| 
 | ||||
|     const password = this.formGroup.controls.password.getRawValue(); | ||||
|     const success  = await this.appService.validateAccount(this.tokenFromUrl, password); | ||||
| 
 | ||||
|     this.storeService.isLoading.set(false); | ||||
| 
 | ||||
|     if (!success) | ||||
|       return; | ||||
| 
 | ||||
|     // NOTE Redirect to home
 | ||||
|     this.router.navigate(['/home']); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										13
									
								
								angular18/src/app/pages/home/home.component.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,13 @@ | |||
| <app-page-layout> | ||||
|   <div class="container my-3"> | ||||
| 
 | ||||
|     <app-progress-bar *ngIf="storeService.isLoading()"></app-progress-bar> | ||||
| 
 | ||||
|     <ng-container *ngIf="!storeService.isLoading()"> | ||||
|       <div class="text-center"> | ||||
|         Let's start the project! | ||||
|       </div> | ||||
|     </ng-container> | ||||
| 
 | ||||
|   </div> | ||||
| </app-page-layout> | ||||
							
								
								
									
										0
									
								
								angular18/src/app/pages/home/home.component.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										60
									
								
								angular18/src/app/pages/home/home.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,60 @@ | |||
| // Angular modules
 | ||||
| import { NgIf }                 from '@angular/common'; | ||||
| import { Component }            from '@angular/core'; | ||||
| import { OnInit }               from '@angular/core'; | ||||
| 
 | ||||
| // Services
 | ||||
| import { StoreService }         from '@services/store.service'; | ||||
| 
 | ||||
| // Components
 | ||||
| import { ProgressBarComponent } from '@blocks/progress-bar/progress-bar.component'; | ||||
| import { PageLayoutComponent }  from '@layouts/page-layout/page-layout.component'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-home', | ||||
|   templateUrl : './home.component.html', | ||||
|   styleUrls   : ['./home.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [PageLayoutComponent, NgIf, ProgressBarComponent] | ||||
| }) | ||||
| export class HomeComponent implements OnInit | ||||
| { | ||||
|   constructor | ||||
|   ( | ||||
|     public storeService : StoreService | ||||
|   ) | ||||
|   { } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
|     setTimeout(_ => | ||||
|     { | ||||
|       this.storeService.isLoading.set(false); | ||||
|     }, 2000); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Computed props -----------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Subscriptions ------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,8 @@ | |||
| <div class="not-found-container"> | ||||
|   <div class="not-found text-center"> | ||||
|     <h1 class="mb-3">{{ 'NOT_FOUND' | translate }}</h1> | ||||
|     <a class="btn btn-lg btn-primary" routerLink="/home"> | ||||
|       {{ 'GO_TO_HOMEPAGE' | translate }} | ||||
|     </a> | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,9 @@ | |||
| .not-found-container { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   min-height: 100vh; | ||||
|   position: relative; | ||||
|   .not-found { | ||||
|     width: 100%; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										42
									
								
								angular18/src/app/pages/not-found/not-found.component.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,42 @@ | |||
| // Angular modules
 | ||||
| import { Component }       from '@angular/core'; | ||||
| import { RouterLink }      from '@angular/router'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-not-found', | ||||
|   templateUrl : './not-found.component.html', | ||||
|   styleUrls   : ['./not-found.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [RouterLink, TranslateModule] | ||||
| }) | ||||
| export class NotFoundComponent | ||||
| { | ||||
|   constructor() { } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Computed props -----------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Subscriptions ------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| } | ||||
|  | @ -0,0 +1,10 @@ | |||
| <!-- How to write CSS keyframes to indeterminate material design progress bar --> | ||||
| <!-- https://stackoverflow.com/a/34773398/7462178 --> | ||||
| 
 | ||||
| <div class="slider"> | ||||
|   <div class="line"></div> | ||||
|   <div class="subline inc"></div> | ||||
|   <div class="subline dec"></div> | ||||
| </div> | ||||
| 
 | ||||
| <div class="text-center my-3" *ngIf="withLabel">{{ 'LOADING' | translate }}...</div> | ||||
|  | @ -0,0 +1,33 @@ | |||
| .slider { | ||||
|   position: relative; | ||||
|   width: 100%; | ||||
|   height: 5px; | ||||
|   overflow-x: hidden; | ||||
|   .line { | ||||
|     position: absolute; | ||||
|     opacity: 0.4; | ||||
|     background: #4a8df8; | ||||
|     width: 150%; | ||||
|     height: 5px; | ||||
|   } | ||||
|   .subline { | ||||
|     position: absolute; | ||||
|     background: #4a8df8; | ||||
|     height: 5px; | ||||
|     &.inc { | ||||
|       animation: increase 2s infinite; | ||||
|     } | ||||
|     &.dec { | ||||
|       animation: decrease 2s 0.5s infinite; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @keyframes increase { | ||||
|   from { left: -5%; width: 5%; } | ||||
|   to { left: 130%; width: 100%;} | ||||
| } | ||||
| @keyframes decrease { | ||||
|   from { left: -80%; width: 80%; } | ||||
|   to { left: 110%; width: 10%;} | ||||
| } | ||||
|  | @ -0,0 +1,27 @@ | |||
| // Angular modules
 | ||||
| import { NgIf }            from '@angular/common'; | ||||
| import { Component }       from '@angular/core'; | ||||
| import { Input }           from '@angular/core'; | ||||
| import { OnInit }          from '@angular/core'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-progress-bar', | ||||
|   templateUrl : './progress-bar.component.html', | ||||
|   styleUrls   : ['./progress-bar.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [NgIf, TranslateModule] | ||||
| }) | ||||
| export class ProgressBarComponent implements OnInit | ||||
| { | ||||
|   @Input() withLabel : boolean = false; | ||||
| 
 | ||||
|   constructor() { } | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,31 @@ | |||
| <ng-container *ngFor="let toast of toastManager.toasts"> | ||||
| 
 | ||||
|   <!-- NOTE With header --> | ||||
|   <ng-container *ngIf="toast.headerKey && toast.withHeader else withoutHeader"> | ||||
| 
 | ||||
|     <ngb-toast class="bg-{{ toast.type }}" | ||||
|       [autohide]="toast.autoHide" | ||||
|       [delay]="toast.delay" | ||||
|       (hide)="toastManager.remove(toast.id)"> | ||||
|       <ng-template ngbToastHeader> | ||||
|         <!-- TODO Icon : Success / Info / Error --> | ||||
|         <div class="text-white me-auto">{{ toast.headerKey | translate }}</div> | ||||
|       </ng-template> | ||||
|       <div class="text-dark">{{ toast.body }}</div> | ||||
|     </ngb-toast> | ||||
| 
 | ||||
|   </ng-container> | ||||
| 
 | ||||
|   <!-- NOTE Without header --> | ||||
|   <ng-template #withoutHeader> | ||||
| 
 | ||||
|     <ngb-toast class="bg-{{ toast.type }}" | ||||
|       [autohide]="toast.autoHide" | ||||
|       [delay]="toast.delay" | ||||
|       (hide)="toastManager.remove(toast.id)"> | ||||
|       <div class="text-dark">{{ toast.body }}</div> | ||||
|     </ngb-toast> | ||||
| 
 | ||||
|   </ng-template> | ||||
| 
 | ||||
| </ng-container> | ||||
|  | @ -0,0 +1,7 @@ | |||
| :host { | ||||
|   position: fixed; | ||||
|   bottom: 0; | ||||
|   right: 0; | ||||
|   margin: 0.5em; | ||||
|   z-index: 1200; | ||||
| } | ||||
|  | @ -0,0 +1,26 @@ | |||
| // Angular modules
 | ||||
| import { NgFor }           from '@angular/common'; | ||||
| import { NgIf }            from '@angular/common'; | ||||
| import { Component }       from '@angular/core'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { NgbToast }        from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { NgbToastHeader }  from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { ToastManager }    from './toast.manager'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-toast', | ||||
|   templateUrl : './toast.component.html', | ||||
|   styleUrls   : ['./toast.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [NgFor, NgIf, NgbToast, NgbToastHeader, TranslateModule] | ||||
| }) | ||||
| export class ToastComponent | ||||
| { | ||||
| 
 | ||||
|   constructor(public toastManager : ToastManager) {} | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,52 @@ | |||
| // Angular modules
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| 
 | ||||
| export type ToastType = 'success' | 'info' | 'warning' | 'danger'; | ||||
| 
 | ||||
| export class Toast | ||||
| { | ||||
|   public   id         !: number | ||||
|   readonly headerKey  ?: string; | ||||
|   public   withHeader  : boolean; | ||||
|   public   body        : string; | ||||
|   public   type        : ToastType; | ||||
|   public   autoHide    : boolean; | ||||
|   public   delay       : number; | ||||
| 
 | ||||
|   constructor(body : string, type ?: ToastType, autoHide : boolean = false) | ||||
|   { | ||||
|     this.withHeader = true; | ||||
|     this.body       = body; | ||||
|     this.type       = type ?? 'danger'; | ||||
|     this.autoHide   = autoHide; | ||||
|     this.delay      = 10000; // 10 sec
 | ||||
| 
 | ||||
|     this.headerKey  = this.type.toUpperCase(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @Injectable({ providedIn : 'root' }) | ||||
| export class ToastManager | ||||
| { | ||||
|   public  toasts  : Toast[] = []; | ||||
|   private counter : number  = 0; | ||||
| 
 | ||||
|   constructor() {} | ||||
| 
 | ||||
|   public show(toast : Toast) : void | ||||
|   { | ||||
|     toast.id = this.counter++; | ||||
|     this.toasts.push(toast); | ||||
|   } | ||||
| 
 | ||||
|   public quickShow(body : string, type ?: ToastType, autoHide : boolean = false) : void | ||||
|   { | ||||
|     const toast = new Toast(body, type, autoHide); | ||||
|     this.show(toast); | ||||
|   } | ||||
| 
 | ||||
|   public remove(id : number) : void | ||||
|   { | ||||
|     this.toasts = this.toasts.filter(t => t.id !== id); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,17 @@ | |||
| <!-- NOTE Confirm form --> | ||||
| <form (ngSubmit)="onClickSubmit()"> | ||||
| 
 | ||||
|   <div class="text-center py-3"> | ||||
|     <p>{{ 'ALERT_DELETE_ENTRY' | translate }}</p> | ||||
|   </div> | ||||
| 
 | ||||
|   <!-- NOTE Submit --> | ||||
|   <div class="text-center"> | ||||
|     <button type="button" class="btn btn-light" (click)="onClickClose()"> | ||||
|       {{ 'CLOSE' | translate }} | ||||
|     </button> | ||||
|     <button type="submit" class="btn btn-danger ms-3"> | ||||
|       {{ 'DELETE' | translate }} | ||||
|     </button> | ||||
|   </div> | ||||
| </form> | ||||
|  | @ -0,0 +1,45 @@ | |||
| // Angular modules
 | ||||
| import { Component }       from '@angular/core'; | ||||
| import { OnInit }          from '@angular/core'; | ||||
| import { Input }           from '@angular/core'; | ||||
| import { Output }          from '@angular/core'; | ||||
| import { EventEmitter }    from '@angular/core'; | ||||
| import { FormsModule }     from '@angular/forms'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-form-confirm', | ||||
|   templateUrl : './form-confirm.component.html', | ||||
|   styleUrls   : ['./form-confirm.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [FormsModule, TranslateModule] | ||||
| }) | ||||
| export class FormConfirmComponent implements OnInit | ||||
| { | ||||
|   @Input()  data        : any; | ||||
|   @Output() submitData  : EventEmitter<boolean> = new EventEmitter(); | ||||
|   @Output() submitClose : EventEmitter<null>    = new EventEmitter(); | ||||
| 
 | ||||
|   constructor() { } | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Action -------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public async onClickSubmit() : Promise<void> | ||||
|   { | ||||
|     this.submitData.emit(true); | ||||
|   } | ||||
| 
 | ||||
|   public onClickClose() : void | ||||
|   { | ||||
|     this.submitClose.emit(); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,35 @@ | |||
| <!-- NOTE Navbar --> | ||||
| <nav class="navbar navbar-expand-lg navbar-light bg-light"> | ||||
|   <div class="container-fluid"> | ||||
|     <a class="navbar-brand" [routerLink]="['/dashboard']"> | ||||
|       <img src="./assets/img/project/logo.svg" [alt]="appName + ' Logo'" class="img-fluid me-2"/> | ||||
|       {{ appName }} | ||||
|     </a> | ||||
|     <button class="navbar-toggler" type="button" (click)="isMenuCollapsed = !isMenuCollapsed" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> | ||||
|       <span class="navbar-toggler-icon"></span> | ||||
|     </button> | ||||
| 
 | ||||
|     <div class="collapse navbar-collapse" [ngbCollapse]="isMenuCollapsed" id="navbarSupportedContent"> | ||||
|       <ul class="navbar-nav mb-2 mb-lg-0 w-100"> | ||||
| 
 | ||||
|         <li class="nav-item"> | ||||
|           <a class="nav-link" [routerLink]="['/home']" [routerLinkActive]="['active']">{{ 'HOME' | translate }}</a> | ||||
|         </li> | ||||
| 
 | ||||
|         <li class="nav-item dropdown" ngbDropdown> | ||||
|           <a class="nav-link dropdown-toggle" ngbDropdownToggle id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false"> | ||||
|             {{ 'USER' | translate }} | ||||
|           </a> | ||||
|           <ul class="dropdown-menu dropdown-menu-end" ngbDropdownMenu aria-labelledby="navbarDropdown"> | ||||
|             <!-- <li><a class="dropdown-item" [routerLink]="['/user']" [routerLinkActive]="['active']">{{ 'MY_ACCOUNT' | translate }}</a></li> --> | ||||
|             <li><a class="dropdown-item cursor-pointer" (click)="onClickLogout()">{{ 'LOGOUT' | translate }}</a></li> | ||||
|           </ul> | ||||
|         </li> | ||||
| 
 | ||||
|       </ul> | ||||
|     </div> | ||||
|   </div> | ||||
| </nav> | ||||
| 
 | ||||
| <!-- NOTE Content --> | ||||
| <ng-content></ng-content> | ||||
|  | @ -0,0 +1,20 @@ | |||
| .navbar-brand { | ||||
|   img { | ||||
|     width: 38px; | ||||
|     display: inline-block; | ||||
|     vertical-align: middle; | ||||
|   } | ||||
|   .title-wrapper { | ||||
|     vertical-align: middle; | ||||
|     display: inline-block; | ||||
|     border-left: 1px solid #adadad; | ||||
|     padding-left: 15px; | ||||
|     margin-left: 15px; | ||||
|     span { | ||||
|       display: block; | ||||
|       &.subtitle { | ||||
|         font-size: 71%; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,73 @@ | |||
| // Angular modules
 | ||||
| import { Component }         from '@angular/core'; | ||||
| import { OnInit }            from '@angular/core'; | ||||
| import { Router }            from '@angular/router'; | ||||
| import { RouterLink }        from '@angular/router'; | ||||
| import { RouterLinkActive }  from '@angular/router'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { NgbCollapse }       from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { NgbDropdown }       from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { NgbDropdownToggle } from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { NgbDropdownMenu }   from '@ng-bootstrap/ng-bootstrap'; | ||||
| import { TranslateModule }   from '@ngx-translate/core'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }       from '@env/environment'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-layout-header', | ||||
|   templateUrl : './layout-header.component.html', | ||||
|   styleUrls   : ['./layout-header.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [RouterLink, NgbCollapse, RouterLinkActive, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, TranslateModule] | ||||
| }) | ||||
| export class LayoutHeaderComponent implements OnInit | ||||
| { | ||||
|   public appName         : string  = environment.appName; | ||||
|   public isMenuCollapsed : boolean = true; | ||||
| 
 | ||||
|   constructor | ||||
|   ( | ||||
|     private router : Router, | ||||
|   ) | ||||
|   { | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public async onClickLogout() : Promise<void> | ||||
|   { | ||||
|     // NOTE Redirect to login
 | ||||
|     this.router.navigate(['/auth/login']); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Computed props -----------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Subscriptions ------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,5 @@ | |||
| <app-layout-header> | ||||
| 
 | ||||
|   <ng-content></ng-content> | ||||
| 
 | ||||
| </app-layout-header> | ||||
|  | @ -0,0 +1,51 @@ | |||
| // Angular modules
 | ||||
| import { Component }             from '@angular/core'; | ||||
| import { OnInit }                from '@angular/core'; | ||||
| 
 | ||||
| // Components
 | ||||
| import { LayoutHeaderComponent } from '../layout-header/layout-header.component'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-page-layout', | ||||
|   templateUrl : './page-layout.component.html', | ||||
|   styleUrls   : ['./page-layout.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [LayoutHeaderComponent] | ||||
| }) | ||||
| export class PageLayoutComponent implements OnInit | ||||
| { | ||||
|   constructor() | ||||
|   { | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
| 
 | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Actions ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Computed props -----------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Requests -----------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Subscriptions ------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,12 @@ | |||
| <div class="modal-header {{ modalData.headerClasses }}"> | ||||
|   <!-- NOTE Dynamic title --> | ||||
|   <h6 class="modal-title">{{ modalData.title }}</h6> | ||||
|   <button type="button" class="btn-close" *ngIf="modalData.closable" aria-label="Close" (click)="activeModal.dismiss()"> | ||||
|   </button> | ||||
| </div> | ||||
| <div class="modal-body auto"> | ||||
| 
 | ||||
|   <!-- NOTE Component --> | ||||
|   <ng-template modal-wrapper-host></ng-template> | ||||
| 
 | ||||
| </div> | ||||
|  | @ -0,0 +1,9 @@ | |||
| .modal-header { | ||||
|   background: var(--bs-info); | ||||
|   &, button { | ||||
|     color: white; | ||||
|   } | ||||
|   .modal-title { | ||||
|     text-transform: uppercase; | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,103 @@ | |||
| // Angular modules
 | ||||
| import { NgIf }                                             from '@angular/common'; | ||||
| import { Component }                                        from '@angular/core'; | ||||
| import { Input }                                            from '@angular/core'; | ||||
| import { OnInit }                                           from '@angular/core'; | ||||
| import { ViewChild }                                        from '@angular/core'; | ||||
| import { EventEmitter }                                     from '@angular/core'; | ||||
| import { Type }                                             from '@angular/core'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { NgbActiveModal }                                   from '@ng-bootstrap/ng-bootstrap'; | ||||
| 
 | ||||
| // Directives
 | ||||
| import { ModalWrapperDirective as ModalWrapperDirective_1 } from '../../../directives/modal-wrapper.directive'; | ||||
| import { ModalWrapperDirective }                            from '@directives/modal-wrapper.directive'; | ||||
| 
 | ||||
| // Dynamic component loader - Angular
 | ||||
| // https://angular.io/guide/dynamic-component-loader
 | ||||
| // https://stackblitz.com/angular/bydvgpxaabj?file=src%2Fapp%2Fad-banner.component.ts
 | ||||
| 
 | ||||
| export class ModalForm | ||||
| { | ||||
|   constructor(public component : Type<any>, public data : any) {} | ||||
| } | ||||
| 
 | ||||
| export interface FormComponent | ||||
| { | ||||
|   data        : any; | ||||
|   submitData  : EventEmitter<any>; | ||||
|   submitClose : EventEmitter<null>; | ||||
| } | ||||
| 
 | ||||
| @Component({ | ||||
|   selector    : 'app-modal-wrapper', | ||||
|   templateUrl : 'modal-wrapper.component.html', | ||||
|   styleUrls   : ['modal-wrapper.component.scss'], | ||||
|   standalone  : true, | ||||
|   imports     : [NgIf, ModalWrapperDirective_1] | ||||
| }) | ||||
| export class ModalWrapperComponent implements OnInit | ||||
| { | ||||
|   @Input() component     : any; | ||||
|   @Input() componentData : any; | ||||
|   @Input() modalData     : { | ||||
|     title         : string, | ||||
|     headerClasses : string, | ||||
|     closable      : boolean, | ||||
|   } = { | ||||
|     title         : '', | ||||
|     headerClasses : '', | ||||
|     closable      : true, | ||||
|   }; | ||||
| 
 | ||||
|   @ViewChild(ModalWrapperDirective, { static : true }) modalWrapperHost !: ModalWrapperDirective; | ||||
| 
 | ||||
|   constructor(public activeModal : NgbActiveModal) | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   public ngOnInit() : void | ||||
|   { | ||||
|     this.loadComponent(); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Init ---------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private loadComponent() : void | ||||
|   { | ||||
|     const modalForm = new ModalForm(this.component, this.componentData); | ||||
| 
 | ||||
|     const viewContainerRef = this.modalWrapperHost.viewContainerRef; | ||||
|     viewContainerRef.clear(); | ||||
| 
 | ||||
|     const componentRef = viewContainerRef.createComponent(modalForm.component); | ||||
| 
 | ||||
|     (<FormComponent>componentRef.instance).data = modalForm.data; | ||||
|     (<FormComponent>componentRef.instance).submitClose.subscribe(() => | ||||
|     { | ||||
|       this.submitClose(); | ||||
|     }); | ||||
|     (<FormComponent>componentRef.instance).submitData.subscribe(event => | ||||
|     { | ||||
|       this.submitData(event); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Helpers ------------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private submitData($event : any) : void | ||||
|   { | ||||
|     this.activeModal.close($event); | ||||
|   } | ||||
| 
 | ||||
|   private submitClose() : void | ||||
|   { | ||||
|     this.activeModal.close(); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,12 @@ | |||
| // Angular modules
 | ||||
| import { Directive }        from '@angular/core'; | ||||
| import { ViewContainerRef } from '@angular/core'; | ||||
| 
 | ||||
| @Directive({ | ||||
|   selector   : '[modal-wrapper-host]', | ||||
|   standalone : true, | ||||
| }) | ||||
| export class ModalWrapperDirective | ||||
| { | ||||
|   constructor(public viewContainerRef : ViewContainerRef) { } | ||||
| } | ||||
							
								
								
									
										6
									
								
								angular18/src/app/shared/enums/endpoint.enum.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,6 @@ | |||
| export enum Endpoint | ||||
| { | ||||
|   AUTHENTICATE     = 'authenticate', | ||||
|   FORGOT_PASSWORD  = 'forgot-password', | ||||
|   VALIDATE_ACCOUNT = 'validate-account', | ||||
| } | ||||
							
								
								
									
										5
									
								
								angular18/src/app/shared/enums/environment.enum.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,5 @@ | |||
| export enum EnvName | ||||
| { | ||||
|   LOCAL = 'local', | ||||
|   PROD  = 'production', | ||||
| } | ||||
							
								
								
									
										4
									
								
								angular18/src/app/shared/enums/storage-key.enum.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,4 @@ | |||
| export enum StorageKey | ||||
| { | ||||
|   TOKEN = 'Token', | ||||
| } | ||||
							
								
								
									
										117
									
								
								angular18/src/app/shared/helpers/storage.helper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,117 @@ | |||
| // Angular modules
 | ||||
| import { Injectable }   from '@angular/core'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }  from '@env/environment'; | ||||
| 
 | ||||
| // Enums
 | ||||
| import { StorageKey }   from '@enums/storage-key.enum'; | ||||
| 
 | ||||
| // Models
 | ||||
| // import { AuthResponse } from '@models/auth-response.model';
 | ||||
| 
 | ||||
| @Injectable() | ||||
| export class StorageHelper | ||||
| { | ||||
|   private static storagePrefix : string = environment.appName + '_' + environment.version + '_'; | ||||
| 
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
|   // SECTION Methods ------------------------------------------------------------------------------
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   // NOTE Token
 | ||||
| 
 | ||||
|   // public static setToken(user : AuthResponse) : void
 | ||||
|   // {
 | ||||
|   //   StorageHelper.setItem(StorageKey.TOKEN, user);
 | ||||
|   // }
 | ||||
| 
 | ||||
|   // public static removeToken() : void
 | ||||
|   // {
 | ||||
|   //   StorageHelper.removeItem(StorageKey.TOKEN);
 | ||||
|   // }
 | ||||
| 
 | ||||
|   // public static getToken() : AuthResponse | null
 | ||||
|   // {
 | ||||
|   //   // Prevent SSR error
 | ||||
|   //   if (typeof localStorage === 'undefined')
 | ||||
|   //     return undefined;
 | ||||
|   //   const data = StorageHelper.getItem(StorageKey.TOKEN);
 | ||||
|   //   return data ? new AuthResponse(data) : null;
 | ||||
|   // }
 | ||||
| 
 | ||||
|   // !SECTION Methods
 | ||||
| 
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
|   // SECTION LocalStorage -------------------------------------------------------------------------
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public static setItem(key : string, value : any, prefix : boolean = true) : void | ||||
|   { | ||||
|     const itemKey = this.prefixer(key, prefix); | ||||
|     localStorage.setItem(itemKey, JSON.stringify(value)); | ||||
|   } | ||||
| 
 | ||||
|   public static getItem(key : string, prefix : boolean = true) : any | ||||
|   { | ||||
|     const itemKey = this.prefixer(key, prefix); | ||||
|     const res = localStorage.getItem(itemKey); | ||||
|     if (res !== 'undefined') | ||||
|       return JSON.parse(res as any); | ||||
|     console.error('StorageHelper : getItem -> undefined key'); | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   public static removeItem(key : string, prefix : boolean = true) : void | ||||
|   { | ||||
|     const itemKey = this.prefixer(key, prefix); | ||||
|     localStorage.removeItem(itemKey); | ||||
|   } | ||||
| 
 | ||||
|   public static getKeys(all : boolean = false) : string[] | ||||
|   { | ||||
|     const keys : string[] = []; | ||||
|     // NOTE Keys
 | ||||
|     for (const key in localStorage) | ||||
|       keys.push(key); | ||||
|     if (all) | ||||
|       return keys; | ||||
|     // NOTE Prefixed keys
 | ||||
|     return keys.filter((item) => item.startsWith(this.storagePrefix)); | ||||
|   } | ||||
| 
 | ||||
|   public static clearItems(all : boolean = false) : void | ||||
|   { | ||||
|     // NOTE Keys
 | ||||
|     if (all) | ||||
|     { | ||||
|       localStorage.clear(); | ||||
|       return; | ||||
|     } | ||||
|     // NOTE Prefixed keys
 | ||||
|     const prefixedKeys = this.getKeys(); | ||||
|     for (const prefixedKey of prefixedKeys) | ||||
|       this.removeItem(prefixedKey, false); | ||||
|   } | ||||
| 
 | ||||
|   public static clearItemsWithoutCurrentPrefix() : void | ||||
|   { | ||||
|     const allKeys = this.getKeys(true); | ||||
|     for (const key of allKeys) | ||||
|       if (!key.startsWith(this.storagePrefix)) | ||||
|         this.removeItem(key, false); | ||||
|   } | ||||
| 
 | ||||
|   // !SECTION LocalStorage
 | ||||
| 
 | ||||
|   // NOTE Private
 | ||||
| 
 | ||||
|   private static prefixer(key : string, autoPrefix : boolean) : string | ||||
|   { | ||||
|     let itemKey = key; | ||||
|     if (autoPrefix) | ||||
|       itemKey = this.storagePrefix + key; | ||||
|     return itemKey; | ||||
|   } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										30
									
								
								angular18/src/app/shared/helpers/string.helper.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,30 @@ | |||
| // Angular modules
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class StringHelper | ||||
| { | ||||
|   /** JavaScript equivalent to printf/String.Format | ||||
|    *  https://stackoverflow.com/a/31007976/7462178
 | ||||
|    */ | ||||
|   public static interpolate(theString : string, argumentArray : string[]) : string | ||||
|   { | ||||
|     let regex = /%s/; | ||||
|     let _r = function(p : string, c : string) { return p.replace(regex, c); }; | ||||
|     return argumentArray.reduce(_r, theString); | ||||
|   } | ||||
| 
 | ||||
|   public static buildURIParams(parameters : object) : string | ||||
|   { | ||||
|     const parts = new URLSearchParams() | ||||
| 
 | ||||
|     for (const [key, value] of Object.entries(parameters)) | ||||
|     { | ||||
|       if (key === undefined || value === undefined) | ||||
|         continue; | ||||
|       parts.append(key, value) | ||||
|     } | ||||
| 
 | ||||
|     return '?' + parts.toString() | ||||
|   } | ||||
| } | ||||
							
								
								
									
										177
									
								
								angular18/src/app/shared/services/app.service.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,177 @@ | |||
| // Angular modules
 | ||||
| import { Injectable }               from '@angular/core'; | ||||
| import { Router }                   from '@angular/router'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { ArrayTyper }               from '@caliatys/array-typer'; | ||||
| import { TranslateService }         from '@ngx-translate/core'; | ||||
| import axios                        from 'axios'; | ||||
| import { AxiosResponse }            from 'axios'; | ||||
| import { AxiosError }               from 'axios'; | ||||
| import { AxiosInstance }            from 'axios'; | ||||
| import { CreateAxiosDefaults }      from 'axios'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { ToastManager }             from '@blocks/toast/toast.manager'; | ||||
| import { environment }              from '@env/environment'; | ||||
| 
 | ||||
| // Helpers
 | ||||
| import { StorageHelper }            from '@helpers/storage.helper'; | ||||
| 
 | ||||
| // Enums
 | ||||
| import { Endpoint }                 from '@enums/endpoint.enum'; | ||||
| 
 | ||||
| // Models
 | ||||
| 
 | ||||
| // Services
 | ||||
| import { StoreService }             from './store.service'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class AppService | ||||
| { | ||||
|   // NOTE Default configuration
 | ||||
|   private default : CreateAxiosDefaults = { | ||||
|     withCredentials : true, | ||||
|     timeout : 990000, | ||||
|     headers : { | ||||
|       'Content-Type' : 'application/json', | ||||
|       'Accept'       : 'application/json', | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   // NOTE Instances
 | ||||
|   private api : AxiosInstance = axios.create({ | ||||
|     baseURL : environment.apiBaseUrl, | ||||
|     ...this.default, | ||||
|   }); | ||||
| 
 | ||||
|   // NOTE Controller
 | ||||
|   private controller : AbortController = new AbortController(); | ||||
| 
 | ||||
|   constructor | ||||
|   ( | ||||
|     private storeService     : StoreService, | ||||
|     private toastManager     : ToastManager, | ||||
|     private router           : Router, | ||||
|     private translateService : TranslateService, | ||||
|   ) | ||||
|   { | ||||
|     this.initRequestInterceptor(this.api); | ||||
|     this.initResponseInterceptor(this.api); | ||||
| 
 | ||||
|     this.initAuthHeader(); | ||||
|   } | ||||
| 
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
|   // SECTION Methods ------------------------------------------------------------------------------
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public async authenticate(email : string, password : string) : Promise<boolean> | ||||
|   { | ||||
|     return Promise.resolve(true); | ||||
| 
 | ||||
|     // StorageHelper.removeToken();
 | ||||
| 
 | ||||
|     // const url      = Endpoint.AUTHENTICATE;
 | ||||
|     // const { data } = await this.api.post(url, { email, password });
 | ||||
| 
 | ||||
|     // if (!data)
 | ||||
|     //   return false;
 | ||||
| 
 | ||||
|     // const authResponse = new AuthResponse(data);
 | ||||
|     // StorageHelper.setToken(authResponse);
 | ||||
|     // this.initAuthHeader();
 | ||||
|     // return true;
 | ||||
|   } | ||||
| 
 | ||||
|   public async forgotPassword(email : string) : Promise<boolean> | ||||
|   { | ||||
|     return Promise.resolve(true); | ||||
| 
 | ||||
|     // const url      = Endpoint.FORGOT_PASSWORD;
 | ||||
|     // const { data } = await this.api.post(url, { email });
 | ||||
| 
 | ||||
|     // return !!data;
 | ||||
|   } | ||||
| 
 | ||||
|   public async validateAccount(token : string, password : string) : Promise<boolean> | ||||
|   { | ||||
|     return Promise.resolve(true); | ||||
| 
 | ||||
|     // const url      = Endpoint.VALIDATE_ACCOUNT;
 | ||||
|     // const { data } = await this.api.post(url, { token, password });
 | ||||
| 
 | ||||
|     // return !!data;
 | ||||
|   } | ||||
| 
 | ||||
|   // public async getLastLines(siteId : string) : Promise<Line[]>
 | ||||
|   // {
 | ||||
|   //   const url      = StringHelper.interpolate(Endpoint.GET_LAST_LINES, [ siteId ]);
 | ||||
|   //   const { data } = await this.api.get(url);
 | ||||
| 
 | ||||
|   //   if (!data)
 | ||||
|   //     return [];
 | ||||
| 
 | ||||
|   //   return ArrayTyper.asArray(Line, data);
 | ||||
|   // }
 | ||||
| 
 | ||||
|   // !SECTION Methods
 | ||||
| 
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
|   // SECTION Helpers ------------------------------------------------------------------------------
 | ||||
|   // ----------------------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   private initAuthHeader() : void | ||||
|   { | ||||
|     // const token = StorageHelper.getToken();
 | ||||
|     // if (!token)
 | ||||
|     //   return;
 | ||||
| 
 | ||||
|     // this.api.defaults.headers.common['Authorization'] = `Bearer ${token.jwtToken}`;
 | ||||
|     // this.api.defaults.headers.common['Token']         = token.jwtToken;
 | ||||
|   } | ||||
| 
 | ||||
|   public initRequestInterceptor(instance : AxiosInstance) : void | ||||
|   { | ||||
|     instance.interceptors.request.use((config) => | ||||
|     { | ||||
|       console.log('interceptors.request.config', config); | ||||
|       this.storeService.isLoading.set(true); | ||||
| 
 | ||||
|       return config; | ||||
|     }, | ||||
|     (error) => | ||||
|     { | ||||
|       console.log('interceptors.request.error', error); | ||||
|       this.storeService.isLoading.set(false); | ||||
| 
 | ||||
|       this.toastManager.quickShow(error); | ||||
|       return Promise.reject(error); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   public initResponseInterceptor(instance : AxiosInstance) : void | ||||
|   { | ||||
|     instance.interceptors.response.use((response) => | ||||
|     { | ||||
|       console.log('interceptors.response.response', response); | ||||
|       this.storeService.isLoading.set(false); | ||||
| 
 | ||||
|       return response; | ||||
|     }, | ||||
|     async (error : AxiosError) => | ||||
|     { | ||||
|       console.log('interceptors.response.error', error); | ||||
|       this.storeService.isLoading.set(false); | ||||
| 
 | ||||
|       // NOTE Prevent request canceled error
 | ||||
|       if (error.code === 'ERR_CANCELED') | ||||
|         return Promise.resolve(error); | ||||
| 
 | ||||
|       this.toastManager.quickShow(error.message); | ||||
|       return Promise.reject(error); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   // !SECTION Helpers
 | ||||
| } | ||||
							
								
								
									
										38
									
								
								angular18/src/app/shared/services/store.service.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,38 @@ | |||
| // Angular modules
 | ||||
| import { isPlatformServer } from '@angular/common'; | ||||
| import { Inject }           from '@angular/core'; | ||||
| import { signal }           from '@angular/core'; | ||||
| import { Injectable }       from '@angular/core'; | ||||
| import { PLATFORM_ID }      from '@angular/core'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { TranslateService } from '@ngx-translate/core'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }      from '@env/environment'; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class StoreService | ||||
| { | ||||
|   public isServer  = signal(isPlatformServer(this.platformId)); | ||||
|   public isLoading = signal(true); | ||||
|   public pageTitle = signal(environment.appName); | ||||
| 
 | ||||
|   constructor | ||||
|   ( | ||||
|     @Inject(PLATFORM_ID) private platformId : Object, | ||||
|     private translateService : TranslateService | ||||
|   ) | ||||
|   { | ||||
|   } | ||||
| 
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
|   // NOTE Page title ---------------------------------------------------------------
 | ||||
|   // -------------------------------------------------------------------------------
 | ||||
| 
 | ||||
|   public setPageTitle(title : string, translate : boolean = true) : void | ||||
|   { | ||||
|     const pageTitle = translate ? this.translateService.instant(title) : title; | ||||
|     this.pageTitle.set(pageTitle); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										0
									
								
								angular18/src/assets/.gitkeep
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										24
									
								
								angular18/src/assets/i18n/en.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,24 @@ | |||
| { | ||||
|   "NOT_FOUND"          : "404 - Page not found", | ||||
|   "GO_TO_HOMEPAGE"     : "Go to homepage", | ||||
|   "WELCOME_TO"         : "Welcome to", | ||||
|   "PROJECT_DESC"       : "A Centurio boilerplate solution", | ||||
|   "SIGN_IN"            : "Sign in", | ||||
|   "LOGOUT"             : "Logout", | ||||
|   "HOME"               : "Home", | ||||
|   "VERSION"            : "Version", | ||||
|   "USER"               : "User", | ||||
|   "EMAIL"              : "E-mail", | ||||
|   "FORGOT_YOUR_PWD"    : "Forgot your password?", | ||||
|   "BACK_TO_LOGIN"      : "Back to login", | ||||
|   "VALIDATE_ACCOUNT"   : "Validate the account", | ||||
|   "VALIDATE"           : "Validate", | ||||
|   "PASSWORD"           : "Password", | ||||
|   "LOADING"            : "Loading", | ||||
|   "CLOSE"              : "Close", | ||||
|   "DELETE"             : "Delete", | ||||
|   "ALERT_DELETE_ENTRY" : "Are you sure you want to delete this entry?", | ||||
|   "RESET_PASSWORD"     : "Reset password", | ||||
|   "FIELD_REQUIRED"     : "This field is required", | ||||
|   "FIELD_EMAIL"        : "This field must be an email" | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/android-chrome-192x192.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/android-chrome-512x512.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 34 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/apple-touch-icon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.2 KiB | 
							
								
								
									
										9
									
								
								angular18/src/assets/img/favicon/browserconfig.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,9 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <browserconfig> | ||||
|   <msapplication> | ||||
|     <tile> | ||||
|       <square150x150logo src="mstile-150x150.png"/> | ||||
|       <TileColor>#2d89ef</TileColor> | ||||
|     </tile> | ||||
|   </msapplication> | ||||
| </browserconfig> | ||||
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/favicon-16x16.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/favicon-32x32.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										21
									
								
								angular18/src/assets/img/favicon/manifest.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,21 @@ | |||
| { | ||||
|   "name": "EasyAngular", | ||||
|   "short_name": "EasyAngular", | ||||
|   "theme_color": "#2b46c7", | ||||
|   "background_color": "#2b46c7", | ||||
|   "display": "standalone", | ||||
|   "scope": "/", | ||||
|   "start_url": "", | ||||
|   "icons": [ | ||||
|     { | ||||
|       "src": "android-chrome-192x192.png", | ||||
|       "sizes": "192x192", | ||||
|       "type": "image/png" | ||||
|     }, | ||||
|     { | ||||
|       "src": "android-chrome-512x512.png", | ||||
|       "sizes": "512x512", | ||||
|       "type": "image/png" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/mstile-144x144.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/mstile-150x150.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/mstile-310x150.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 3.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/mstile-310x310.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/favicon/mstile-70x70.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.3 KiB | 
							
								
								
									
										44
									
								
								angular18/src/assets/img/favicon/safari-pinned-tab.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,44 @@ | |||
| <?xml version="1.0" standalone="no"?> | ||||
| <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" | ||||
|  "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> | ||||
| <svg version="1.0" xmlns="http://www.w3.org/2000/svg" | ||||
|  width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000" | ||||
|  preserveAspectRatio="xMidYMid meet"> | ||||
| <metadata> | ||||
| Created by potrace 1.11, written by Peter Selinger 2001-2013 | ||||
| </metadata> | ||||
| <g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)" | ||||
| fill="#000000" stroke="none"> | ||||
| <path d="M3298 6995 c-2 -1 -48 -6 -102 -9 -55 -4 -102 -8 -105 -10 -3 -2 -35 | ||||
| -7 -70 -10 -35 -4 -85 -11 -110 -16 -25 -5 -57 -12 -71 -14 -83 -14 -199 -42 | ||||
| -304 -73 -125 -36 -364 -119 -381 -133 -5 -4 -21 -11 -35 -15 -42 -13 -228 | ||||
| -105 -340 -169 -188 -106 -468 -302 -453 -317 7 -7 538 -291 873 -467 58 -30 | ||||
| 128 -67 155 -82 28 -15 88 -48 135 -72 185 -99 249 -132 340 -181 52 -27 118 | ||||
| -62 145 -77 28 -15 100 -53 160 -85 61 -32 133 -70 160 -85 28 -15 89 -48 138 | ||||
| -74 222 -118 417 -221 502 -266 39 -21 216 -115 395 -210 179 -95 358 -190 | ||||
| 398 -211 39 -22 107 -58 150 -80 42 -23 109 -58 147 -79 39 -20 108 -58 155 | ||||
| -82 47 -25 119 -63 160 -85 41 -22 120 -64 175 -93 55 -30 145 -77 200 -107 | ||||
| 55 -29 190 -101 301 -159 l201 -106 1 -1156 c0 -637 1 -1161 1 -1165 2 -22 | ||||
| 225 294 308 438 57 98 153 292 203 410 71 166 165 468 195 624 56 291 69 431 | ||||
| 69 711 0 186 -6 326 -19 420 -3 19 -7 53 -10 75 -50 407 -218 897 -435 1265 | ||||
| -53 90 -191 301 -214 327 -18 21 -31 37 -55 71 -87 124 -326 373 -501 523 | ||||
| -121 104 -424 319 -449 319 -5 0 -11 4 -13 8 -8 20 -306 171 -473 239 -196 81 | ||||
| -434 153 -630 192 -126 25 -213 38 -375 57 -56 6 -516 15 -522 9z"/> | ||||
| <path d="M1260 6188 c-96 -77 -312 -289 -391 -383 -19 -22 -42 -49 -53 -60 | ||||
| -37 -40 -164 -212 -233 -315 -180 -270 -311 -541 -411 -850 -38 -118 -79 -264 | ||||
| -88 -320 -3 -14 -11 -56 -19 -95 -15 -78 -23 -127 -39 -247 -27 -197 -28 -722 | ||||
| -1 -828 2 -8 6 -40 10 -71 3 -31 8 -67 10 -80 3 -13 7 -39 10 -58 2 -18 7 -41 | ||||
| 9 -50 3 -9 8 -31 11 -49 13 -75 77 -306 119 -430 223 -647 652 -1236 1196 | ||||
| -1641 435 -324 918 -542 1415 -640 97 -19 127 -24 210 -36 22 -3 54 -8 70 -10 | ||||
| 120 -19 544 -26 705 -11 146 13 164 15 195 20 17 3 48 8 70 12 22 3 74 12 115 | ||||
| 20 41 9 89 18 105 21 53 9 327 91 425 127 169 61 484 208 518 240 11 11 17 | ||||
| 279 13 556 -1 32 -6 37 -59 65 -31 16 -91 48 -132 71 -41 22 -131 70 -200 106 | ||||
| -69 36 -161 85 -205 109 -44 23 -129 69 -190 101 -60 32 -129 69 -152 80 l-43 | ||||
| 22 -1 60 c0 34 0 269 -1 523 0 328 -4 465 -12 471 -6 5 -73 41 -149 81 -76 39 | ||||
| -157 82 -180 95 -51 29 -210 113 -462 246 -55 29 -122 65 -150 80 -27 15 -99 | ||||
| 53 -160 85 -60 32 -163 86 -227 121 -64 35 -119 64 -121 64 -3 0 -40 19 -83 | ||||
| 43 -73 40 -130 71 -379 202 -49 26 -117 62 -150 80 -33 18 -105 56 -160 85 | ||||
| -129 68 -219 116 -325 173 -191 102 -298 159 -347 184 l-53 26 -2 1013 -3 | ||||
| 1012 -25 -20z"/> | ||||
| </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 2.7 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/project/folder-structure.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 14 KiB | 
							
								
								
									
										
											BIN
										
									
								
								angular18/src/assets/img/project/login.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 679 KiB | 
							
								
								
									
										7
									
								
								angular18/src/assets/img/project/logo.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 25 KiB | 
							
								
								
									
										48
									
								
								angular18/src/assets/scss/project.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,48 @@ | |||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Browser style : Reset --------------------------------------------------------- Overwrite | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Project --------------------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Link | ||||
| 
 | ||||
| // NOTE Scrollbar | ||||
| 
 | ||||
| // NOTE Sortable table -------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Icons ----------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Bootstrap --------------------------------------------------------------------- Overwrite | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Pagination ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| // NOTE Button ---------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Modal ----------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Toast ----------------------------------------------------------------------------------- | ||||
| 
 | ||||
| .toast-header { | ||||
|   background-color: transparent; | ||||
| } | ||||
| .toast-body { | ||||
|   background-color: rgba(255, 255, 255, 0.85); | ||||
| } | ||||
| 
 | ||||
| // NOTE Nav ------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Table ----------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Select ---------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Form ------------------------------------------------------------------------------------ | ||||
| 
 | ||||
| // Checkbox | ||||
| 
 | ||||
| // Radio | ||||
| 
 | ||||
| // NOTE Sortable table -------------------------------------------------------------------------- | ||||
							
								
								
									
										80
									
								
								angular18/src/assets/scss/styles.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,80 @@ | |||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Variables ------------------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| @import 'variables.scss'; | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Bootstrap ------------------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Complete bootstrap | ||||
| @import '../../../node_modules/bootstrap/scss/bootstrap.scss'; | ||||
| 
 | ||||
| // NOTE Bootstrap configuration | ||||
| // @import '../../../node_modules/bootstrap/scss/functions'; | ||||
| // @import '../../../node_modules/bootstrap/scss/variables'; | ||||
| // @import '../../../node_modules/bootstrap/scss/mixins'; | ||||
| 
 | ||||
| // // NOTE Merge maps | ||||
| // $theme-colors: map-merge($theme-colors, $custom-colors); | ||||
| 
 | ||||
| // @import '../../../node_modules/bootstrap/scss/utilities'; | ||||
| 
 | ||||
| // // NOTE Bootstrap layout & components | ||||
| // @import '../../../node_modules/bootstrap/scss/root'; | ||||
| // @import '../../../node_modules/bootstrap/scss/reboot'; | ||||
| // @import '../../../node_modules/bootstrap/scss/type'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/images'; | ||||
| // @import '../../../node_modules/bootstrap/scss/containers'; | ||||
| // @import '../../../node_modules/bootstrap/scss/grid'; | ||||
| // @import '../../../node_modules/bootstrap/scss/tables'; | ||||
| // @import '../../../node_modules/bootstrap/scss/forms'; | ||||
| // @import '../../../node_modules/bootstrap/scss/buttons'; | ||||
| // @import '../../../node_modules/bootstrap/scss/transitions'; | ||||
| // @import '../../../node_modules/bootstrap/scss/dropdown'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/button-group'; | ||||
| // @import '../../../node_modules/bootstrap/scss/nav'; | ||||
| // @import '../../../node_modules/bootstrap/scss/navbar'; | ||||
| // @import '../../../node_modules/bootstrap/scss/card'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/accordion'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/breadcrumb'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/pagination'; | ||||
| // @import '../../../node_modules/bootstrap/scss/badge'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/alert'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/progress'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/list-group'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/close'; | ||||
| // @import '../../../node_modules/bootstrap/scss/toasts'; | ||||
| // @import '../../../node_modules/bootstrap/scss/modal'; | ||||
| // @import '../../../node_modules/bootstrap/scss/tooltip'; | ||||
| // @import '../../../node_modules/bootstrap/scss/popover'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/carousel'; | ||||
| // @import '../../../node_modules/bootstrap/scss/spinners'; | ||||
| // // @import '../../../node_modules/bootstrap/scss/offcanvas'; | ||||
| 
 | ||||
| // // NOTE Boostrap helpers | ||||
| // @import '../../../node_modules/bootstrap/scss/helpers'; | ||||
| 
 | ||||
| // // NOTE Boostrap utilities | ||||
| // @import '../../../node_modules/bootstrap/scss/utilities/api'; | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE External components --------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // $roboto-font-path: "../../../node_modules/roboto-fontface/fonts" !default; | ||||
| // @import "../../../node_modules/roboto-fontface/css/roboto/sass/roboto-fontface"; | ||||
| // @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;400;700&display=swap'); | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Project style --------------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| @import 'project.scss'; | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Overwrite styles ------------------------------------------------------------------------ | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // @import 'modules/_bootstrap.scss'; | ||||
							
								
								
									
										66
									
								
								angular18/src/assets/scss/variables.scss
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,66 @@ | |||
| // ---------------------------------------------------------------------------------------------- | ||||
| // SECTION Variables ---------------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Project --------------------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Colors ---------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| // NOTE Bootstrap ------------------------------------------------------------------------------- | ||||
| // ---------------------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Utilities ------------------------------------------------------------------------------- | ||||
| 
 | ||||
| $utilities: ( | ||||
|   "cursor": ( | ||||
|     property: cursor, | ||||
|     class: cursor, | ||||
|     responsive: false, | ||||
|     values: pointer, | ||||
|   ) | ||||
| ); | ||||
| 
 | ||||
| // NOTE Color system ---------------------------------------------------------------------------- | ||||
| 
 | ||||
| // $primary: #375F9B; | ||||
| $secondary: #8492A6; | ||||
| $text-muted: #ACA7AA; | ||||
| $info: #375F9B; // #3C78DA; | ||||
| $danger: #D0021B; | ||||
| $success: #12a366; | ||||
| $min-contrast-ratio: 2.5; | ||||
| 
 | ||||
| // NOTE Body ------------------------------------------------------------------------------------ | ||||
| 
 | ||||
| // NOTE Grid ------------------------------------------------------------------------------------ | ||||
| 
 | ||||
| // NOTE Borders --------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Typography  ----------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Links ----------------------------------------------------------------------------------- | ||||
| // NOTE Badges ---------------------------------------------------------------------------------- | ||||
| // NOTE Buttons --------------------------------------------------------------------------------- | ||||
| 
 | ||||
| $btn-close-color: white; | ||||
| 
 | ||||
| // NOTE Navbar ---------------------------------------------------------------------------------- | ||||
| // NOTE Navs ------------------------------------------------------------------------------------ | ||||
| 
 | ||||
| // NOTE Modal ----------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Dropdowns ------------------------------------------------------------------------------- | ||||
| // NOTE Cards ----------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE List group ------------------------------------------------------------------------------ | ||||
| // NOTE Pagination ------------------------------------------------------------------------------ | ||||
| // NOTE Popovers -------------------------------------------------------------------------------- | ||||
| // NOTE Typography ------------------------------------------------------------------------------ | ||||
| // NOTE Forms ----------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // NOTE Tables ---------------------------------------------------------------------------------- | ||||
| 
 | ||||
| // !SECTION Variables | ||||
							
								
								
									
										21
									
								
								angular18/src/environments/environment.prod.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,21 @@ | |||
| // Enums
 | ||||
| import { EnvName } from '@enums/environment.enum'; | ||||
| 
 | ||||
| // Packages
 | ||||
| import packageInfo from '../../package.json'; | ||||
| 
 | ||||
| const scheme = 'http://'; | ||||
| const host   = 'localhost'; | ||||
| const port   = ':5000'; | ||||
| const path   = '/api/'; | ||||
| 
 | ||||
| const baseUrl = scheme + host + port + path; | ||||
| 
 | ||||
| export const environment = { | ||||
|   production      : true, | ||||
|   version         : packageInfo.version, | ||||
|   appName         : 'EasyAngular', | ||||
|   envName         : EnvName.PROD, | ||||
|   defaultLanguage : 'en', | ||||
|   apiBaseUrl      : baseUrl, | ||||
| }; | ||||
							
								
								
									
										34
									
								
								angular18/src/environments/environment.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,34 @@ | |||
| // 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`.
 | ||||
| 
 | ||||
| // Enums
 | ||||
| import { EnvName } from '@enums/environment.enum'; | ||||
| 
 | ||||
| // Packages
 | ||||
| import packageInfo from '../../package.json'; | ||||
| 
 | ||||
| const scheme = 'http://'; | ||||
| const host   = 'localhost'; | ||||
| const port   = ':5000'; | ||||
| const path   = '/api/'; | ||||
| 
 | ||||
| const baseUrl = scheme + host + port + path; | ||||
| 
 | ||||
| export const environment = { | ||||
|   production      : false, | ||||
|   version         : packageInfo.version, | ||||
|   appName         : 'EasyAngular', | ||||
|   envName         : EnvName.LOCAL, | ||||
|   defaultLanguage : 'en', | ||||
|   apiBaseUrl      : baseUrl, | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
|  * 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.
 | ||||
							
								
								
									
										25
									
								
								angular18/src/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,25 @@ | |||
| <!doctype html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|   <meta charset="utf-8"> | ||||
|   <title>EasyAngular</title> | ||||
|   <base href="/"> | ||||
| 
 | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| 
 | ||||
|   <link rel="icon" type="image/x-icon" href="./assets/img/favicon/favicon.ico"> | ||||
| 
 | ||||
|   <!-- NOTE RealFavIconGenerator --> | ||||
|   <link rel="apple-touch-icon" sizes="180x180" href="assets/img/favicon/apple-touch-icon.png"/> | ||||
|   <link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon/favicon-32x32.png"/> | ||||
|   <link rel="icon" type="image/png" sizes="16x16" href="assets/img/favicon/favicon-16x16.png"/> | ||||
|   <link rel="manifest" href="assets/img/favicon/manifest.json"/> | ||||
|   <link rel="mask-icon" href="assets/img/favicon/safari-pinned-tab.svg" color="#5bbad5"/> | ||||
|   <meta name="msapplication-TileColor" content="#da532c"/> | ||||
|   <meta name="theme-color" content="#ffffff"/> | ||||
| 
 | ||||
| </head> | ||||
| <body> | ||||
|   <app-root></app-root> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										24
									
								
								angular18/src/main.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,24 @@ | |||
| /// <reference types="@angular/localize" />
 | ||||
| 
 | ||||
| // Angular modules
 | ||||
| import { enableProdMode }       from '@angular/core'; | ||||
| import { bootstrapApplication } from '@angular/platform-browser'; | ||||
| 
 | ||||
| // External modules
 | ||||
| import { appConfig }            from './app/app.config'; | ||||
| 
 | ||||
| // Internal modules
 | ||||
| import { environment }          from './environments/environment'; | ||||
| 
 | ||||
| // Components
 | ||||
| import { AppComponent }         from './app/app.component'; | ||||
| 
 | ||||
| if (environment.production) { | ||||
|   enableProdMode(); | ||||
| } | ||||
| 
 | ||||
| bootstrapApplication( | ||||
|   AppComponent, | ||||
|   appConfig | ||||
| ) | ||||
| .catch(err => console.error(err)); | ||||
							
								
								
									
										54
									
								
								angular18/src/polyfills.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,54 @@ | |||
| /** | ||||
|  * 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 recent versions of Safari, Chrome (including | ||||
|  * Opera), Edge on the desktop, and iOS and Chrome on mobile. | ||||
|  * | ||||
|  * Learn more in https://angular.io/guide/browser-support
 | ||||
|  */ | ||||
| 
 | ||||
| /*************************************************************************************************** | ||||
|  * BROWSER POLYFILLS | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * 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 | ||||
|  */ | ||||
| 
 | ||||
| import '@angular/localize/init'; | ||||
							
								
								
									
										14
									
								
								angular18/src/test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,14 @@ | |||
| // 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'; | ||||
| 
 | ||||
| // First, initialize the Angular testing environment.
 | ||||
| getTestBed().initTestEnvironment( | ||||
|   BrowserDynamicTestingModule, | ||||
|   platformBrowserDynamicTesting(), | ||||
| ); | ||||
 Tykayn
						Tykayn