Merge remote-tracking branch 'origin/main'
# Conflicts: # admin/src/app/app.html # admin/src/app/app.ts
This commit is contained in:
commit
5ab072992b
@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"start": "ng serve --proxy-config proxy.conf.json",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
|
||||
8
admin/proxy.conf.json
Normal file
8
admin/proxy.conf.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:3000",
|
||||
"secure": false,
|
||||
"logLevel": "debug",
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,23 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
|
||||
import {
|
||||
ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection,
|
||||
provideZonelessChangeDetection,
|
||||
} from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { routes } from './app.routes';
|
||||
import { JwtInterceptor } from './auth/jwt.interceptor';
|
||||
import { AuthService } from './auth/auth.service';
|
||||
import { AuthGuard } from './auth/auth.guard';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZonelessChangeDetection(),
|
||||
provideRouter(routes)
|
||||
]
|
||||
provideRouter(routes),
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
AuthService,
|
||||
AuthGuard,
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,14 +1,5 @@
|
||||
<div class="row">
|
||||
|
||||
<div>
|
||||
<button (click)="logout()">Logout</button>
|
||||
</div>
|
||||
<button class="btn btn-primary">Default</button>
|
||||
<div class="join">
|
||||
<button class="join-item btn">1</button>
|
||||
<button class="join-item btn btn-active">2</button>
|
||||
<button class="join-item btn">3</button>
|
||||
<button class="join-item btn">4</button>
|
||||
</div>
|
||||
<app-admin-layout>
|
||||
|
||||
</app-admin-layout>
|
||||
<app-main-menu></app-main-menu>
|
||||
<router-outlet />
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { AuthGuard } from './auth/auth.guard';
|
||||
import { HomeComponent } from './components/home/home.component'; // Assuming you have a HomeComponent
|
||||
|
||||
export const routes: Routes = [];
|
||||
export const routes: Routes = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: '', component: HomeComponent, canActivate: [AuthGuard] },
|
||||
{ path: '**', redirectTo: '' } // Redirect to home for any other route
|
||||
];
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import { Router, RouterOutlet } from '@angular/router';
|
||||
import { MainMenu } from './components/main-menu/main-menu';
|
||||
import { AuthService } from './auth/auth.service';
|
||||
import { AdminLayout } from './layout/admin-layout/admin-layout';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet, AdminLayout],
|
||||
imports: [RouterOutlet, AdminLayout,MainMenu],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.css'
|
||||
})
|
||||
export class App {
|
||||
protected readonly title = signal('admin');
|
||||
|
||||
constructor(private authService: AuthService, private router: Router) {}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout();
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
}
|
||||
|
||||
23
admin/src/app/auth/auth.guard.ts
Normal file
23
admin/src/app/auth/auth.guard.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private authService: AuthService, private router: Router) {}
|
||||
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
if (this.authService.isLoggedIn()) {
|
||||
return true;
|
||||
} else {
|
||||
// Redirect to the login page
|
||||
return this.router.createUrlTree(['/login']);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
admin/src/app/auth/auth.service.ts
Normal file
36
admin/src/app/auth/auth.service.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
private readonly TOKEN_KEY = 'access_token';
|
||||
private apiUrl = 'http://localhost:4200/api/auth'; // Adjust if your server URL is different
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
login(credentials: { username: string; password: string }): Observable<any> {
|
||||
return this.http.post<{ access_token: string }>(`${this.apiUrl}/login`, credentials).pipe(
|
||||
tap((response) => this.setToken(response.access_token))
|
||||
);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
localStorage.removeItem(this.TOKEN_KEY);
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
return localStorage.getItem(this.TOKEN_KEY);
|
||||
}
|
||||
|
||||
isLoggedIn(): boolean {
|
||||
return this.getToken() !== null;
|
||||
}
|
||||
|
||||
private setToken(token: string): void {
|
||||
localStorage.setItem(this.TOKEN_KEY, token);
|
||||
}
|
||||
}
|
||||
29
admin/src/app/auth/jwt.interceptor.ts
Normal file
29
admin/src/app/auth/jwt.interceptor.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
HttpEvent,
|
||||
HttpHandler,
|
||||
HttpInterceptor,
|
||||
HttpRequest,
|
||||
} from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtInterceptor implements HttpInterceptor {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
intercept(
|
||||
request: HttpRequest<any>,
|
||||
next: HttpHandler
|
||||
): Observable<HttpEvent<any>> {
|
||||
const token = this.authService.getToken();
|
||||
if (token) {
|
||||
request = request.clone({
|
||||
setHeaders: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
8
admin/src/app/components/home/home.component.ts
Normal file
8
admin/src/app/components/home/home.component.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
template: '<h1>Welcome to the Admin Panel!</h1>',
|
||||
standalone: true,
|
||||
})
|
||||
export class HomeComponent {}
|
||||
15
admin/src/app/components/login/login.component.html
Normal file
15
admin/src/app/components/login/login.component.html
Normal file
@ -0,0 +1,15 @@
|
||||
<div>
|
||||
<h2>Login</h2>
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input id="username" formControlName="username" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input id="password" type="password" formControlName="password" />
|
||||
</div>
|
||||
<button type="submit" [disabled]="loginForm.invalid">Log In</button>
|
||||
</form>
|
||||
<p *ngIf="errorMessage">{{ errorMessage }}</p>
|
||||
</div>
|
||||
36
admin/src/app/components/login/login.component.ts
Normal file
36
admin/src/app/components/login/login.component.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthService } from '../../auth/auth.service';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
standalone: true,
|
||||
imports: [ReactiveFormsModule, CommonModule],
|
||||
})
|
||||
export class LoginComponent {
|
||||
loginForm: FormGroup;
|
||||
errorMessage: string = '';
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
) {
|
||||
this.loginForm = this.fb.group({
|
||||
username: ['', Validators.required],
|
||||
password: ['', Validators.required],
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.loginForm.valid) {
|
||||
this.authService.login(this.loginForm.value).subscribe({
|
||||
next: () => this.router.navigate(['/']),
|
||||
error: (err) => (this.errorMessage = 'Invalid username or password'),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
0
admin/src/app/components/main-menu/main-menu.css
Normal file
0
admin/src/app/components/main-menu/main-menu.css
Normal file
1
admin/src/app/components/main-menu/main-menu.html
Normal file
1
admin/src/app/components/main-menu/main-menu.html
Normal file
@ -0,0 +1 @@
|
||||
<p>main-menu works!</p>
|
||||
23
admin/src/app/components/main-menu/main-menu.spec.ts
Normal file
23
admin/src/app/components/main-menu/main-menu.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MainMenu } from './main-menu';
|
||||
|
||||
describe('MainMenu', () => {
|
||||
let component: MainMenu;
|
||||
let fixture: ComponentFixture<MainMenu>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [MainMenu]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MainMenu);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
11
admin/src/app/components/main-menu/main-menu.ts
Normal file
11
admin/src/app/components/main-menu/main-menu.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-menu',
|
||||
imports: [],
|
||||
templateUrl: './main-menu.html',
|
||||
styleUrl: './main-menu.css',
|
||||
})
|
||||
export class MainMenu {
|
||||
|
||||
}
|
||||
0
admin/src/app/components/navbar/navbar.css
Normal file
0
admin/src/app/components/navbar/navbar.css
Normal file
19
admin/src/app/components/navbar/navbar.html
Normal file
19
admin/src/app/components/navbar/navbar.html
Normal file
@ -0,0 +1,19 @@
|
||||
<div class="navbar bg-base-100 shadow-sm">
|
||||
<div class="flex-1">
|
||||
<a class="btn btn-ghost text-xl">daisyUI</a>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
<li><a>Link</a></li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>Parent</summary>
|
||||
<ul class="bg-base-100 rounded-t-none p-2">
|
||||
<li><a>Link 1</a></li>
|
||||
<li><a>Link 2</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
23
admin/src/app/components/navbar/navbar.spec.ts
Normal file
23
admin/src/app/components/navbar/navbar.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Navbar } from './navbar';
|
||||
|
||||
describe('Navbar', () => {
|
||||
let component: Navbar;
|
||||
let fixture: ComponentFixture<Navbar>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Navbar]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Navbar);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
11
admin/src/app/components/navbar/navbar.ts
Normal file
11
admin/src/app/components/navbar/navbar.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
imports: [],
|
||||
templateUrl: './navbar.html',
|
||||
styleUrl: './navbar.css',
|
||||
})
|
||||
export class Navbar {
|
||||
|
||||
}
|
||||
@ -7,6 +7,8 @@ async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.useLogger(app.get(DvbookingLoggerService));
|
||||
|
||||
app.setGlobalPrefix('api');
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('DV Booking API')
|
||||
.setDescription('The DV Booking API description')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user