authentication
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<!-- <div class="example-sidenav-content">
|
||||
<button type="button" mat-button (click)="drawer.toggle()">
|
||||
Toggle sidenav
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
<!-- <div class="content" style="width: 100%; height: 100%;"> -->
|
||||
<!-- The AG Grid component, with Dimensions, CSS Theme, Row Data, and Column Definition -->
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
|
||||
[rowData]="rowData"
|
||||
[columnDefs]="colDefs"
|
||||
[defaultColDef]="defaultColDef"
|
||||
/>
|
||||
|
||||
<!-- </div> -->
|
||||
@@ -1,5 +1,8 @@
|
||||
:host {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +1,36 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
|
||||
import { Component, inject, LOCALE_ID } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { AgGridAngular } from 'ag-grid-angular'; // Angular Data Grid Component
|
||||
import { ColDef } from 'ag-grid-community'; // Column Definition Type Interface
|
||||
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [RouterOutlet, AgGridAngular],
|
||||
providers: [[{ provide: LOCALE_ID, useValue: 'de-DE' }]],
|
||||
imports: [RouterOutlet,],
|
||||
providers: [
|
||||
{ provide: LOCALE_ID, useValue: 'de-DE' },
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'client';
|
||||
|
||||
defaultColDef: ColDef = {
|
||||
flex: 1,
|
||||
editable: true
|
||||
};
|
||||
|
||||
|
||||
private http: HttpClient = inject(HttpClient);
|
||||
|
||||
constructor() {
|
||||
this.http.get('api/').subscribe({
|
||||
next: n => {
|
||||
console.log(n)
|
||||
},
|
||||
error: e => {
|
||||
console.log(e)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
rowData = [
|
||||
{ make: "Tesla", model: "Model Y", price: 64950, electric: true },
|
||||
{ make: "Ford", model: "F-Series", price: 33850, electric: false },
|
||||
{ make: "Toyota", model: "Corolla", price: 29600, electric: false },
|
||||
];
|
||||
|
||||
// Column Definitions: Defines the columns to be displayed.
|
||||
colDefs: ColDef[] = [
|
||||
{ field: "make" },
|
||||
{
|
||||
field: "model",
|
||||
cellEditor: 'agSelectCellEditor',
|
||||
singleClickEdit: true,
|
||||
cellEditorParams: {
|
||||
values: ['English', 'Spanish', 'French', 'Portuguese', '(other)'],
|
||||
}
|
||||
},
|
||||
{ field: "price", type: 'number'
|
||||
// cellEditor: 'agDateCellEditor',
|
||||
// cellEditorParams: {
|
||||
// min: '2000-01-01',
|
||||
// max: '2019-12-31',
|
||||
// }
|
||||
},
|
||||
{ field: "electric", editable: true }
|
||||
];
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHotToastConfig } from '@ngxpert/hot-toast';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { tokenInterceptor } from './core/interceptor/token.interceptor';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient()]
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient(withInterceptors([tokenInterceptor]))
|
||||
, provideHotToastConfig({
|
||||
stacking: "depth",
|
||||
visibleToasts: 5,
|
||||
position: 'top-center',
|
||||
theme: 'toast',
|
||||
autoClose: true,
|
||||
dismissible: false,
|
||||
duration: 5000
|
||||
}), provideAnimationsAsync()]
|
||||
};
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AuthenticatedGuard } from './core/auth/auth.guard';
|
||||
import { StartComponent } from './modules/start/start.component';
|
||||
import { LoginComponent } from './modules/auth/login/login.component';
|
||||
import { LayoutComponent } from './core/layout/layout.component';
|
||||
import { DashboardComponent } from './modules/dashboard/dashboard.component';
|
||||
import { AllUsersComponent } from './modules/admin/all-users/all-users.component';
|
||||
|
||||
export const routes: Routes = [];
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: LayoutComponent, canActivate: [AuthenticatedGuard], children: [
|
||||
{ path: '', component: DashboardComponent },
|
||||
{ path: 'users', component: AllUsersComponent }
|
||||
]},
|
||||
{ path: 'login', component: LoginComponent},
|
||||
];
|
||||
|
||||
36
client/src/app/core/auth/auth.guard.ts
Normal file
36
client/src/app/core/auth/auth.guard.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, Router } from "@angular/router";
|
||||
import { HotToastService } from "@ngxpert/hot-toast";
|
||||
import { AuthService } from "./auth.service";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthenticatedGuard {
|
||||
public isLoading = false;
|
||||
private router = inject(Router);
|
||||
private toast = inject(HotToastService);
|
||||
private authService = inject(AuthService);
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot):
|
||||
Promise<boolean> {
|
||||
this.isLoading = true;
|
||||
|
||||
if (this.authService.authenticated) { return true; }
|
||||
|
||||
const s = await this.authService.getMe();
|
||||
if (s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const authCode = route.queryParams["code"];
|
||||
if (authCode) {
|
||||
const success = await this.authService.authenticateWithCode(authCode);
|
||||
if (success) { return true; }
|
||||
}
|
||||
|
||||
this.router.navigateByUrl('/login');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
108
client/src/app/core/auth/auth.service.ts
Normal file
108
client/src/app/core/auth/auth.service.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { BehaviorSubject, Observable, tap, of, catchError } from 'rxjs';
|
||||
import { IUser } from '../../model/interface/user.interface';
|
||||
import { environment } from '../../../environments/environment.development';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
|
||||
private accessTokenSubject = new BehaviorSubject<string | null>(null);
|
||||
private refreshToken: string | null = null;
|
||||
private http: HttpClient = inject(HttpClient);
|
||||
private route: ActivatedRoute = inject(ActivatedRoute);
|
||||
|
||||
private user: IUser | null = null;
|
||||
|
||||
constructor() {
|
||||
const token = localStorage.getItem('accessToken_vault');
|
||||
const refresh = localStorage.getItem('refreshToken_vault');
|
||||
|
||||
this.accessTokenSubject.next(token);
|
||||
this.refreshToken = refresh;
|
||||
}
|
||||
|
||||
|
||||
getMe() {
|
||||
if (!this.getAccessToken()) {
|
||||
return false;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.http.get<IUser>('/api/auth/me').subscribe({
|
||||
next: user => {
|
||||
this.user = user;
|
||||
resolve(true)
|
||||
},
|
||||
error: () => {
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
authenticateWithCode(authcode: string) {
|
||||
return new Promise(resolve => {
|
||||
this.http.post<IUser>('/api/auth/auth-code', { code: authcode }).subscribe(user => {
|
||||
this.setTokens({ accessToken: user.accessToken, refreshToken: user.refreshToken});
|
||||
this.user = user;
|
||||
return resolve(true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
get authenticated(): boolean {
|
||||
return this.user != null;
|
||||
}
|
||||
|
||||
|
||||
login(credentials: { username: string; password: string }): Observable<any> {
|
||||
return this.http.post<any>('/api/auth/login', credentials).pipe(
|
||||
tap(tokens => {
|
||||
this.setTokens(tokens);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private setTokens(tokens: { accessToken: string; refreshToken: string }) {
|
||||
this.accessTokenSubject.next(tokens.accessToken);
|
||||
this.refreshToken = tokens.refreshToken;
|
||||
localStorage.setItem('accessToken_vault', tokens.accessToken);
|
||||
localStorage.setItem('refreshToken_vault', tokens.refreshToken);
|
||||
}
|
||||
|
||||
getAccessToken(): string | null {
|
||||
return this.accessTokenSubject.value;
|
||||
}
|
||||
|
||||
refreshAccessToken(): Observable<any> {
|
||||
if (!this.refreshToken) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
return this.http.post<any>('/api/auth/refresh', { refreshToken: this.refreshToken }).pipe(
|
||||
tap(tokens => {
|
||||
this.setTokens(tokens);
|
||||
}),
|
||||
catchError(() => {
|
||||
this.logout();
|
||||
return of(null);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.accessTokenSubject.next(null);
|
||||
this.refreshToken = null;
|
||||
localStorage.removeItem('accessToken_vault');
|
||||
localStorage.removeItem('refreshToken_vault');
|
||||
}
|
||||
|
||||
public routeToLogin() {
|
||||
const url = `https://sso.beantastic.de?client_id=ffc46841-26f8-4946-a57a-5a9f8f21bc13&redirect_uri=${environment.location}`;
|
||||
location.href = url;
|
||||
}
|
||||
}
|
||||
52
client/src/app/core/interceptor/token.interceptor.ts
Normal file
52
client/src/app/core/interceptor/token.interceptor.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
HttpEvent,
|
||||
HttpRequest,
|
||||
HttpErrorResponse,
|
||||
HttpInterceptorFn,
|
||||
HttpHandlerFn
|
||||
} from '@angular/common/http';
|
||||
import { Observable, catchError, switchMap, throwError } from 'rxjs';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { HotToastService } from '@ngxpert/hot-toast';
|
||||
|
||||
export const tokenInterceptor: HttpInterceptorFn = (
|
||||
req: HttpRequest<any>,
|
||||
next: HttpHandlerFn
|
||||
): Observable<HttpEvent<any>> => {
|
||||
const authService: AuthService = inject(AuthService);
|
||||
const toast: HotToastService = inject(HotToastService);
|
||||
const accessToken = authService.getAccessToken();
|
||||
let authReq = req;
|
||||
|
||||
if (accessToken) {
|
||||
authReq = req.clone({
|
||||
setHeaders: { Authorization: `Bearer ${accessToken}` }
|
||||
});
|
||||
}
|
||||
|
||||
return next(authReq).pipe(
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
if (error.status === 401) {
|
||||
return authService.refreshAccessToken().pipe(
|
||||
switchMap(() => {
|
||||
const newAccessToken = authService.getAccessToken();
|
||||
const newAuthReq = req.clone({
|
||||
setHeaders: { Authorization: `Bearer ${newAccessToken}` }
|
||||
});
|
||||
return next(newAuthReq);
|
||||
}),
|
||||
catchError(err => {
|
||||
authService.logout();
|
||||
toast.error(err.error.message)
|
||||
return throwError(() => err);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return throwError(() => error);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
29
client/src/app/core/layout/layout.component.html
Normal file
29
client/src/app/core/layout/layout.component.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<mat-toolbar>
|
||||
<button mat-icon-button (click)="drawer.toggle()">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<span>Keyvault</span>
|
||||
<span class="example-spacer"></span>
|
||||
<button mat-icon-button class="example-icon favorite-icon" aria-label="Example icon-button with heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button class="example-icon" aria-label="Example icon-button with share icon">
|
||||
<mat-icon>share</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-drawer-container class="example-container" autosize>
|
||||
<mat-drawer #drawer class="main_sidenav" mode="side" opened="true">
|
||||
sdf
|
||||
</mat-drawer>
|
||||
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<!-- <div class="example-sidenav-content">
|
||||
<button type="button" mat-button (click)="drawer.toggle()">
|
||||
Toggle sidenav
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
</mat-drawer-container>
|
||||
24
client/src/app/core/layout/layout.component.scss
Normal file
24
client/src/app/core/layout/layout.component.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
mat-drawer-container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.example-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
mat-drawer, mat-toolbar {
|
||||
background-color: #fff;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.main_sidenav{
|
||||
width: 200px;
|
||||
}
|
||||
23
client/src/app/core/layout/layout.component.spec.ts
Normal file
23
client/src/app/core/layout/layout.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LayoutComponent } from './layout.component';
|
||||
|
||||
describe('LayoutComponent', () => {
|
||||
let component: LayoutComponent;
|
||||
let fixture: ComponentFixture<LayoutComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LayoutComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LayoutComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
17
client/src/app/core/layout/layout.component.ts
Normal file
17
client/src/app/core/layout/layout.component.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-layout',
|
||||
standalone: true,
|
||||
imports: [MatButtonModule, MatIconModule, MatSidenavModule, RouterModule, MatToolbarModule],
|
||||
templateUrl: './layout.component.html',
|
||||
styleUrl: './layout.component.scss'
|
||||
})
|
||||
export class LayoutComponent {
|
||||
|
||||
}
|
||||
8
client/src/app/model/interface/user.interface.ts
Normal file
8
client/src/app/model/interface/user.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface IUser {
|
||||
username: string;
|
||||
id: string;
|
||||
lastName: string;
|
||||
firstName: String;
|
||||
refreshToken: string;
|
||||
accessToken: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@if (gridOptions || true) {
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
(gridReady)="onGridReady($event)"
|
||||
[gridOptions]="gridOptions!"
|
||||
/>
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AllUsersComponent } from './all-users.component';
|
||||
|
||||
describe('AllUsersComponent', () => {
|
||||
let component: AllUsersComponent;
|
||||
let fixture: ComponentFixture<AllUsersComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AllUsersComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AllUsersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ApiService } from '../../../shared/api.service';
|
||||
import { AgGridAngular } from 'ag-grid-angular';
|
||||
import { GridOptions,GridApi, GridReadyEvent, CellEditingStoppedEvent } from 'ag-grid-community';
|
||||
import { HotToastService } from '@ngxpert/hot-toast';
|
||||
|
||||
@Component({
|
||||
selector: 'app-all-users',
|
||||
standalone: true,
|
||||
imports: [AgGridAngular],
|
||||
templateUrl: './all-users.component.html',
|
||||
styleUrl: './all-users.component.scss'
|
||||
})
|
||||
export class AllUsersComponent {
|
||||
|
||||
private toast: HotToastService = inject(HotToastService);
|
||||
private api: ApiService = inject(ApiService);
|
||||
|
||||
gridApi!: GridApi;
|
||||
|
||||
private roles: string [] = [];
|
||||
|
||||
gridOptions: GridOptions = {
|
||||
rowData: [],
|
||||
columnDefs: [
|
||||
{ field: 'username' , headerName: 'User', flex: 1, editable: true, sort: 'asc' },
|
||||
{ field: 'firstName', headerName: 'Vorname', flex: 1, editable: true},
|
||||
{ field: 'lastName', headerName: 'Nachname', flex: 1, editable: true},
|
||||
{ field: 'isActive', headerName: 'Aktiv', flex: 1, editable: true, },
|
||||
{ field: 'role', headerName: 'Rolle', flex: 1, editable: true, cellEditor: 'agSelectCellEditor',
|
||||
cellEditorParams: {
|
||||
values: ['user', 'develop', 'admin'],
|
||||
},
|
||||
singleClickEdit: true,
|
||||
},
|
||||
],
|
||||
loading: true,
|
||||
overlayLoadingTemplate: 'Lade Daten...'
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
loadUsers() {
|
||||
this.api.getAllUsers().subscribe({
|
||||
next: n => {
|
||||
this.gridApi.setGridOption("rowData", n)
|
||||
this.gridApi.setGridOption("loading", false);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
cellEditEnd(params: CellEditingStoppedEvent, self: AllUsersComponent) {
|
||||
if (!params.valueChanged) { return; }
|
||||
|
||||
self.api.saveUser(params.data)
|
||||
.pipe(
|
||||
self.toast.observe({
|
||||
loading: 'speichern...',
|
||||
success: 'Änderungen gespeichert',
|
||||
error: 'Änderungen konnten nicht gespeichert werden!'
|
||||
})
|
||||
).subscribe({
|
||||
error: () => {
|
||||
const data = self.gridApi.getRowNode(params.node.id as string);
|
||||
data?.setDataValue(params.colDef.field as string, params.oldValue)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onGridReady(params: GridReadyEvent) {
|
||||
this.gridApi = params.api;
|
||||
const self = this;
|
||||
this.gridApi.addEventListener("cellEditingStopped", evt => this.cellEditEnd(evt, self))
|
||||
this.loadUsers();
|
||||
console.log(params)
|
||||
}
|
||||
|
||||
}
|
||||
1
client/src/app/modules/auth/login/login.component.html
Normal file
1
client/src/app/modules/auth/login/login.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<button mat-flat-button color="primary" (click)="authService.routeToLogin()">Login</button>
|
||||
23
client/src/app/modules/auth/login/login.component.spec.ts
Normal file
23
client/src/app/modules/auth/login/login.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
14
client/src/app/modules/auth/login/login.component.ts
Normal file
14
client/src/app/modules/auth/login/login.component.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AuthService } from '../../../core/auth/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
standalone: true,
|
||||
imports: [MatButtonModule],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss'
|
||||
})
|
||||
export class LoginComponent {
|
||||
public authService: AuthService = inject(AuthService);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<ag-grid-angular
|
||||
style="width: 100%; height: 100%;"
|
||||
|
||||
[rowData]="rowData"
|
||||
[columnDefs]="colDefs"
|
||||
[defaultColDef]="defaultColDef"
|
||||
/>
|
||||
23
client/src/app/modules/dashboard/dashboard.component.spec.ts
Normal file
23
client/src/app/modules/dashboard/dashboard.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
|
||||
describe('DashboardComponent', () => {
|
||||
let component: DashboardComponent;
|
||||
let fixture: ComponentFixture<DashboardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DashboardComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DashboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
51
client/src/app/modules/dashboard/dashboard.component.ts
Normal file
51
client/src/app/modules/dashboard/dashboard.component.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { AgGridAngular } from 'ag-grid-angular';
|
||||
import { ColDef } from 'ag-grid-community'; // Column Definition Type Interface
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
standalone: true,
|
||||
imports: [AgGridAngular],
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrl: './dashboard.component.scss'
|
||||
})
|
||||
export class DashboardComponent {
|
||||
|
||||
defaultColDef: ColDef = {
|
||||
flex: 1,
|
||||
editable: true
|
||||
};
|
||||
rowData = [
|
||||
{ make: "Tesla", model: "Model Y", price: 64950, electric: true },
|
||||
{ make: "Ford", model: "F-Series", price: 33850, electric: false },
|
||||
{ make: "Toyota", model: "Corolla", price: 29600, electric: false },
|
||||
{ make: "Tesla", model: "Model Y", price: 64950, electric: true },
|
||||
{ make: "Ford", model: "F-Series", price: 33850, electric: false },
|
||||
{ make: "Toyota", model: "Corolla", price: 29600, electric: false },
|
||||
{ make: "Tesla", model: "Model Y", price: 64950, electric: true },
|
||||
{ make: "Ford", model: "F-Series", price: 33850, electric: false },
|
||||
{ make: "Toyota", model: "Corolla", price: 29600, electric: false },
|
||||
{ make: "Tesla", model: "Model Y", price: 64950, electric: true },
|
||||
];
|
||||
|
||||
// Column Definitions: Defines the columns to be displayed.
|
||||
colDefs: ColDef[] = [
|
||||
{ field: "make" },
|
||||
{
|
||||
field: "model",
|
||||
cellEditor: 'agSelectCellEditor',
|
||||
singleClickEdit: true,
|
||||
cellEditorParams: {
|
||||
values: ['English', 'Spanish', 'French', 'Portuguese', '(other)'],
|
||||
}
|
||||
},
|
||||
{ field: "price", type: 'number'
|
||||
// cellEditor: 'agDateCellEditor',
|
||||
// cellEditorParams: {
|
||||
// min: '2000-01-01',
|
||||
// max: '2019-12-31',
|
||||
// }
|
||||
},
|
||||
{ field: "electric", editable: true }
|
||||
];
|
||||
}
|
||||
1
client/src/app/modules/start/start.component.html
Normal file
1
client/src/app/modules/start/start.component.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>start works!</p>
|
||||
0
client/src/app/modules/start/start.component.scss
Normal file
0
client/src/app/modules/start/start.component.scss
Normal file
23
client/src/app/modules/start/start.component.spec.ts
Normal file
23
client/src/app/modules/start/start.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { StartComponent } from './start.component';
|
||||
|
||||
describe('StartComponent', () => {
|
||||
let component: StartComponent;
|
||||
let fixture: ComponentFixture<StartComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [StartComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(StartComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
19
client/src/app/modules/start/start.component.ts
Normal file
19
client/src/app/modules/start/start.component.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, inject } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-start',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './start.component.html',
|
||||
styleUrl: './start.component.scss'
|
||||
})
|
||||
export class StartComponent {
|
||||
private http: HttpClient = inject(HttpClient);
|
||||
|
||||
ngOnInit(): void {
|
||||
this.http.get('/api/').subscribe(res => {
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
26
client/src/app/shared/api.service.ts
Normal file
26
client/src/app/shared/api.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { IUser } from '../model/interface/user.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
private http: HttpClient = inject(HttpClient);
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
||||
getAllUsers(): Observable<IUser[]> {
|
||||
return this.http.get<IUser[]>('/api/user');
|
||||
}
|
||||
|
||||
saveUser(user: IUser) {
|
||||
return this.http.post('/api/user', user);
|
||||
}
|
||||
|
||||
getRoles(): Observable<{id: string, name: string}[]> {
|
||||
return this.http.get<{id: string, name: string}[]>('/api/role');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user