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",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve --proxy-config proxy.conf.json",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test"
|
"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 { provideRouter } from '@angular/router';
|
||||||
|
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
|
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { routes } from './app.routes';
|
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 = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
provideZonelessChangeDetection(),
|
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>
|
</div>
|
||||||
<button class="btn btn-primary">Default</button>
|
<app-main-menu></app-main-menu>
|
||||||
<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>
|
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
import { Routes } from '@angular/router';
|
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 { 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';
|
import { AdminLayout } from './layout/admin-layout/admin-layout';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
imports: [RouterOutlet, AdminLayout],
|
imports: [RouterOutlet, AdminLayout,MainMenu],
|
||||||
templateUrl: './app.html',
|
templateUrl: './app.html',
|
||||||
styleUrl: './app.css'
|
styleUrl: './app.css'
|
||||||
})
|
})
|
||||||
export class App {
|
export class App {
|
||||||
protected readonly title = signal('admin');
|
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);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.useLogger(app.get(DvbookingLoggerService));
|
app.useLogger(app.get(DvbookingLoggerService));
|
||||||
|
|
||||||
|
app.setGlobalPrefix('api');
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('DV Booking API')
|
.setTitle('DV Booking API')
|
||||||
.setDescription('The DV Booking API description')
|
.setDescription('The DV Booking API description')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user