feat: enhance application structure and improve accessibility
- Refactor various components for better readability and maintainability. - Update HTML templates to include `alt` attributes for images and `for` attributes for labels. - Implement reactive forms in OTP component and improve token management in AuthService. - Adjust routing to redirect to 'admin/about' by default. - Remove deprecated interceptor implementation and streamline authentication logic. - Add console logs for better debugging during initialization.
This commit is contained in:
parent
d0025d55ef
commit
38f305067a
@ -14,13 +14,15 @@
|
|||||||
|
|
||||||
@for (category of model.projectsCategories; track category) {
|
@for (category of model.projectsCategories; track category) {
|
||||||
<li class="filter-item">
|
<li class="filter-item">
|
||||||
<button (click)="filterProjects(category)" [ngClass]="{active: filter === category}">{{category}}</button>
|
<button (click)="filterProjects(category)"
|
||||||
|
[ngClass]="{active: filter === category}">{{category}}</button>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="filter-select-box" (click)="categoryClicked = !categoryClicked">
|
<div class="filter-select-box" (click)="categoryClicked = !categoryClicked" tabindex="0"
|
||||||
|
(keyup.enter)="categoryClicked = !categoryClicked">
|
||||||
|
|
||||||
<button class="filter-select" [ngClass]="{active: categoryClicked}">
|
<button class="filter-select" [ngClass]="{active: categoryClicked}">
|
||||||
|
|
||||||
@ -58,7 +60,7 @@
|
|||||||
<ion-icon name="eye-outline"></ion-icon>
|
<ion-icon name="eye-outline"></ion-icon>
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<img src="{{imagesOrigin + project.imagePath}}" loading="lazy">
|
<img src="{{imagesOrigin + project.imagePath}}" alt="{{project.name}}" loading="lazy">
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<h3 class="project-title">{{project.name}}</h3>
|
<h3 class="project-title">{{project.name}}</h3>
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
<li class="timeline-item">
|
<li class="timeline-item">
|
||||||
|
|
||||||
<h4 class="h4 timeline-item-title">{{education.degree}}{{education.degreeSpecialization != null ? " - "
|
<h4 class="h4 timeline-item-title">{{education.degree}}{{education.degreeSpecialization !== null ? " - "
|
||||||
+ education.degreeSpecialization : ""}}</h4>
|
+ education.degreeSpecialization : ""}}</h4>
|
||||||
|
|
||||||
<span>{{education.period}}</span>
|
<span>{{education.period}}</span>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule, isPlatformBrowser, isPlatformServer } from '@angular/common';
|
||||||
import { BaseComponent } from '../base.component';
|
import { BaseComponent } from '../base.component';
|
||||||
import { IResume } from './resume.model';
|
import { IResume } from './resume.model';
|
||||||
import { AdminService } from '../services/admin.service';
|
import { AdminService } from '../services/admin.service';
|
||||||
@ -11,6 +11,8 @@ import { AdminService } from '../services/admin.service';
|
|||||||
imports: [CommonModule]
|
imports: [CommonModule]
|
||||||
})
|
})
|
||||||
export class Resume extends BaseComponent<IResume> implements OnInit {
|
export class Resume extends BaseComponent<IResume> implements OnInit {
|
||||||
|
platformId = inject(PLATFORM_ID);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const svc = inject(AdminService);
|
const svc = inject(AdminService);
|
||||||
super(svc);
|
super(svc);
|
||||||
@ -19,6 +21,8 @@ export class Resume extends BaseComponent<IResume> implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
console.log("Resume component initialized");
|
console.log("Resume component initialized");
|
||||||
|
console.log("Server:", isPlatformServer(this.platformId));
|
||||||
|
console.log("Browser:", isPlatformBrowser(this.platformId));
|
||||||
if(!this.isDataLoading()) {
|
if(!this.isDataLoading()) {
|
||||||
this.isDataLoading.set(true);
|
this.isDataLoading.set(true);
|
||||||
this.getResume();
|
this.getResume();
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { IAbout } from '../about/about.model';
|
import { IAbout } from '../about/about.model';
|
||||||
import { ICv } from '../models/cv.model';
|
import { ICv } from '../models/cv.model';
|
||||||
import { IContactModel } from '../contact/contact.model';
|
import { IContactModel } from '../contact/contact.model';
|
||||||
import { IResume } from '../resume/resume.model';
|
import { IResume } from '../resume/resume.model';
|
||||||
import { IProjects } from '../projects/projects.model';
|
import { IProjects } from '../projects/projects.model';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable, of, Subject } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -19,8 +19,7 @@ export class AdminService {
|
|||||||
public candidateAndSocialLinks!: IContactModel;
|
public candidateAndSocialLinks!: IContactModel;
|
||||||
public resume!: IResume;
|
public resume!: IResume;
|
||||||
public projects!: IProjects;
|
public projects!: IProjects;
|
||||||
|
private http: HttpClient = inject(HttpClient);
|
||||||
constructor(private http: HttpClient) { }
|
|
||||||
|
|
||||||
private api(path: string) {
|
private api(path: string) {
|
||||||
return `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`;
|
return `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`;
|
||||||
|
|||||||
@ -1,18 +1,16 @@
|
|||||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
|
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
import { provideHttpClient, withFetch, withInterceptors} from '@angular/common/http';
|
||||||
import { provideHttpClient, withFetch, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
|
|
||||||
import { httpInterceptorProviders } from './interceptors';
|
|
||||||
import { loadingInterceptor } from './interceptors/loading-interceptor';
|
import { loadingInterceptor } from './interceptors/loading-interceptor';
|
||||||
|
import { AuthInterceptor } from './interceptors/auth-interceptor';
|
||||||
|
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
provideZonelessChangeDetection(),
|
provideZonelessChangeDetection(),
|
||||||
provideHttpClient(withInterceptorsFromDi(), withInterceptors([loadingInterceptor]), withFetch()),
|
provideHttpClient(withInterceptors([loadingInterceptor, AuthInterceptor]), withFetch()),
|
||||||
httpInterceptorProviders,
|
|
||||||
provideRouter(routes), provideClientHydration(withEventReplay())
|
provideRouter(routes), provideClientHydration(withEventReplay())
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const enum AdminRouteTitles {
|
|||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'admin',
|
redirectTo: 'admin/about',
|
||||||
pathMatch: 'full'
|
pathMatch: 'full'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -24,11 +24,6 @@ export const routes: Routes = [
|
|||||||
component: AdminLayout,
|
component: AdminLayout,
|
||||||
title: 'Admin',
|
title: 'Admin',
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
redirectTo: 'about', // ✔ correct relative redirect
|
|
||||||
pathMatch: 'full'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: OtpComponent,
|
component: OtpComponent,
|
||||||
|
|||||||
@ -13,4 +13,7 @@ import { RouterModule } from "@angular/router";
|
|||||||
export class App {
|
export class App {
|
||||||
loader = inject(LoaderService);
|
loader = inject(LoaderService);
|
||||||
protected readonly title = signal('portfolio-admin');
|
protected readonly title = signal('portfolio-admin');
|
||||||
|
constructor(){
|
||||||
|
console.log('🎯 AppComponent initialized', { time: Date.now() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,13 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { OtpComponent } from './otp/otp.component';
|
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { AuthInterceptor } from '../interceptors/auth-interceptor';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
|
||||||
OtpComponent
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
ReactiveFormsModule
|
|
||||||
],
|
|
||||||
providers:[
|
|
||||||
AuthInterceptor
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AuthModule { }
|
export class AuthModule { }
|
||||||
|
|||||||
@ -20,41 +20,50 @@ export class AuthService {
|
|||||||
private readonly baseUrl = (environment.apiUrl ?? '').replace(/\/+$/, '');
|
private readonly baseUrl = (environment.apiUrl ?? '').replace(/\/+$/, '');
|
||||||
private accessToken: string | null = null;
|
private accessToken: string | null = null;
|
||||||
private platformId = inject(PLATFORM_ID);
|
private platformId = inject(PLATFORM_ID);
|
||||||
private accessTokenSub = new BehaviorSubject<string | null>(null);
|
public accessTokenSub = new BehaviorSubject<string | null>(null);
|
||||||
tokenReady$ = new BehaviorSubject<boolean>(false);
|
|
||||||
http = inject(HttpClient);
|
http = inject(HttpClient);
|
||||||
router = inject(Router);
|
router = inject(Router);
|
||||||
|
|
||||||
private readonly storageKey = 'accessToken';
|
private readonly storageKey = 'accessToken';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
console.log('🔥 AuthService constructor started');
|
||||||
|
this.accessToken = this.safeGetToken();
|
||||||
|
console.log('🔥 AuthService constructor finished', { accessToken: !!this.accessToken });
|
||||||
|
}
|
||||||
|
|
||||||
private api(path: string) {
|
private api(path: string) {
|
||||||
return `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`;
|
return `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call on app start, or from guard
|
// // Call on app start, or from guard
|
||||||
async ensureTokenReady(): Promise<void>{
|
// async ensureTokenReady(): Promise<void>{
|
||||||
if(this.tokenReady$.value) return;
|
// if(this.tokenReady$.value) return;
|
||||||
|
|
||||||
const stored = this.safeGetToken();
|
// const stored = this.safeGetToken();
|
||||||
|
|
||||||
// try to restore from storage
|
// // try to restore from storage
|
||||||
if(stored){
|
// if(stored){
|
||||||
this.accessTokenSub.next(stored);
|
// this.accessTokenSub.next(stored);
|
||||||
this.tokenReady$.next(true);
|
// this.tokenReady$.next(true);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// // Optionally: try a silent refresh on startup to restore session using HttpOnly cookie
|
// // // Optionally: try a silent refresh on startup to restore session using HttpOnly cookie
|
||||||
// try {
|
// // try {
|
||||||
// const res = await firstValueFrom(this.refreshToken());
|
// // const res = await firstValueFrom(this.refreshToken());
|
||||||
// this.safeSetToken(res.accessToken);
|
// // this.safeSetToken(res.accessToken);
|
||||||
// }
|
// // }
|
||||||
// catch{
|
// // catch{
|
||||||
// console.warn('Silent token refresh failed on startup');
|
// // console.warn('Silent token refresh failed on startup');
|
||||||
// }
|
// // }
|
||||||
// finally{
|
// // finally{
|
||||||
// this.tokenReady$.next(true);
|
// // this.tokenReady$.next(true);
|
||||||
// }
|
// // }
|
||||||
|
// }
|
||||||
|
|
||||||
|
get currentToken(): string | null {
|
||||||
|
return this.safeGetToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
safeSetToken(token: string) {
|
safeSetToken(token: string) {
|
||||||
@ -62,12 +71,19 @@ export class AuthService {
|
|||||||
|
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
localStorage.setItem(this.storageKey, token);
|
localStorage.setItem(this.storageKey, token);
|
||||||
|
this.accessTokenSub.next(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private safeGetToken(): string | null {
|
private safeGetToken(): string | null {
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
try {
|
||||||
return localStorage.getItem(this.storageKey);
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
const token = localStorage.getItem(this.storageKey);
|
||||||
|
this.accessTokenSub.next(token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to read from localStorage:', e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -77,6 +93,7 @@ export class AuthService {
|
|||||||
|
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
localStorage.removeItem(this.storageKey);
|
localStorage.removeItem(this.storageKey);
|
||||||
|
this.accessTokenSub.next(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +117,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAccessToken(): string | null {
|
getAccessToken(): string | null {
|
||||||
return this.accessToken ?? this.safeGetToken();
|
return this.accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
logout(): Observable<void> {
|
logout(): Observable<void> {
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
<!-- Step 1: Enter Email -->
|
<!-- Step 1: Enter Email -->
|
||||||
@if (!isOtpSent()) {
|
@if (!isOtpSent()) {
|
||||||
<form [formGroup]="emailForm" (ngSubmit)="sendOtp()" class="form-section">
|
<form [formGroup]="emailForm" (ngSubmit)="sendOtp()" class="form-section">
|
||||||
<label>Email Address</label>
|
<label for="email">Email Address</label>
|
||||||
<input type="email" formControlName="email" placeholder="Enter your email" />
|
<input id="email" type="email" formControlName="email" placeholder="Enter your email" />
|
||||||
<button type="submit" [disabled]="emailForm.invalid">Send OTP</button>
|
<button type="submit" [disabled]="emailForm.invalid">Send OTP</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
@ -20,8 +20,8 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form [formGroup]="otpForm" (ngSubmit)="verifyOtp()">
|
<form [formGroup]="otpForm" (ngSubmit)="verifyOtp()">
|
||||||
<label>Enter 6-digit OTP</label>
|
<label for="otp">Enter 6-digit OTP</label>
|
||||||
<input type="text" maxlength="6" formControlName="otp" placeholder="123456" />
|
<input id="otp" type="text" maxlength="6" formControlName="otp" placeholder="123456" />
|
||||||
<button type="submit" [disabled]="otpForm.invalid">
|
<button type="submit" [disabled]="otpForm.invalid">
|
||||||
Verify OTP
|
Verify OTP
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import { Component, inject, OnInit, signal } from '@angular/core';
|
import { Component, inject, OnInit, signal } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { AuthService } from '../auth.service';
|
import { AuthService } from '../auth.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-otp',
|
selector: 'app-otp',
|
||||||
templateUrl: './otp.component.html',
|
templateUrl: './otp.component.html',
|
||||||
styleUrls: ['./otp.component.scss']
|
styleUrls: ['./otp.component.scss'],
|
||||||
|
imports: [ReactiveFormsModule, CommonModule]
|
||||||
})
|
})
|
||||||
export class OtpComponent implements OnInit {
|
export class OtpComponent implements OnInit {
|
||||||
emailForm: FormGroup;
|
emailForm: FormGroup;
|
||||||
|
|||||||
@ -2,19 +2,11 @@ import { inject } from '@angular/core';
|
|||||||
import { CanActivateFn, Router } from '@angular/router';
|
import { CanActivateFn, Router } from '@angular/router';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
export const authGuard: CanActivateFn = async (route, state) => {
|
export const authGuard: CanActivateFn = (route, state) => {
|
||||||
const auth = inject(AuthService);
|
const auth = inject(AuthService);
|
||||||
const router = inject(Router);
|
const router = inject(Router);
|
||||||
|
|
||||||
await auth.ensureTokenReady();
|
return auth.currentToken
|
||||||
|
? true
|
||||||
// No token → not logged in
|
: router.parseUrl(`/admin/login?returnUrl=${state.url}`);
|
||||||
if (!auth.isLoggedIn()) {
|
|
||||||
router.navigate(['admin/login'], {
|
|
||||||
queryParams: { returnUrl: state.url }
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
import { inject, Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
HttpHandler,
|
HttpHandlerFn,
|
||||||
HttpEvent,
|
HttpEvent,
|
||||||
HttpInterceptor,
|
HttpErrorResponse,
|
||||||
HttpErrorResponse
|
HttpInterceptorFn
|
||||||
} from '@angular/common/http';
|
} from '@angular/common/http';
|
||||||
import { BehaviorSubject, catchError, filter, Observable, switchMap, take, throwError } from 'rxjs';
|
import { BehaviorSubject, catchError, filter, Observable, switchMap, take, throwError } from 'rxjs';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
@Injectable()
|
export const AuthInterceptor: HttpInterceptorFn = (
|
||||||
export class AuthInterceptor implements HttpInterceptor {
|
req: HttpRequest<unknown>,
|
||||||
|
next: HttpHandlerFn
|
||||||
|
): Observable<HttpEvent<unknown>> => {
|
||||||
|
|
||||||
private refreshInProgress = false;
|
let refreshInProgress = false;
|
||||||
private refreshSubject = new BehaviorSubject<string | null>(null);
|
const refreshSubject = new BehaviorSubject<string | null>(null);
|
||||||
authSvc: AuthService = inject(AuthService);
|
const authSvc: AuthService = inject(AuthService);
|
||||||
|
let headers = req.headers;
|
||||||
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
// add API key
|
||||||
|
const apiKey = authSvc.getApiKey();
|
||||||
let headers = req.headers;
|
|
||||||
|
|
||||||
// add API key
|
|
||||||
const apiKey = this.authSvc.getApiKey();
|
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
headers = headers.set('XApiKey', apiKey);
|
headers = headers.set('XApiKey', apiKey);
|
||||||
}
|
}
|
||||||
@ -35,7 +33,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add access token
|
// add access token
|
||||||
const token = this.authSvc.getAccessToken();
|
const token = authSvc.currentToken;
|
||||||
if (token) {
|
if (token) {
|
||||||
headers = headers.set('Authorization', `Bearer ${token}`);
|
headers = headers.set('Authorization', `Bearer ${token}`);
|
||||||
}
|
}
|
||||||
@ -46,7 +44,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return next.handle(clonedRequest).pipe(
|
return next(clonedRequest).pipe(
|
||||||
catchError((err: HttpErrorResponse) => {
|
catchError((err: HttpErrorResponse) => {
|
||||||
|
|
||||||
if (err.status !== 401) {
|
if (err.status !== 401) {
|
||||||
@ -54,8 +52,8 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if refresh is already in progress
|
// if refresh is already in progress
|
||||||
if (this.refreshInProgress) {
|
if (refreshInProgress) {
|
||||||
return this.refreshSubject.pipe(
|
return refreshSubject.pipe(
|
||||||
filter(t => t !== null),
|
filter(t => t !== null),
|
||||||
take(1),
|
take(1),
|
||||||
switchMap(newToken => {
|
switchMap(newToken => {
|
||||||
@ -65,41 +63,39 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return next.handle(retryReq);
|
return next(retryReq);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// start refresh
|
// start refresh
|
||||||
this.refreshInProgress = true;
|
refreshInProgress = true;
|
||||||
this.refreshSubject.next(null);
|
refreshSubject.next(null);
|
||||||
|
|
||||||
return this.authSvc.refreshToken()!.pipe(
|
return authSvc.refreshToken()!.pipe(
|
||||||
switchMap(res => {
|
switchMap(res => {
|
||||||
|
|
||||||
this.refreshInProgress = false;
|
refreshInProgress = false;
|
||||||
const newToken = res.accessToken;
|
const newToken = res.accessToken;
|
||||||
this.authSvc.safeSetToken(newToken);
|
authSvc.safeSetToken(newToken);
|
||||||
|
|
||||||
this.refreshSubject.next(newToken);
|
|
||||||
|
|
||||||
|
refreshSubject.next(newToken);
|
||||||
const retryReq = clonedRequest.clone({
|
const retryReq = clonedRequest.clone({
|
||||||
headers: clonedRequest.headers.set('Authorization', `Bearer ${newToken}`),
|
headers: clonedRequest.headers.set('Authorization', `Bearer ${newToken}`),
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return next.handle(retryReq);
|
return next(retryReq);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
catchError(refreshErr => {
|
catchError(refreshErr => {
|
||||||
this.refreshInProgress = false;
|
refreshInProgress = false;
|
||||||
this.refreshSubject.next(null);
|
refreshSubject.next(null);
|
||||||
|
|
||||||
this.authSvc.logout();
|
authSvc.logout();
|
||||||
return throwError(() => refreshErr);
|
return throwError(() => refreshErr);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import { HTTP_INTERCEPTORS } from "@angular/common/http";
|
|
||||||
import { AuthInterceptor } from "./auth-interceptor";
|
|
||||||
|
|
||||||
|
|
||||||
export const httpInterceptorProviders = [
|
|
||||||
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}
|
|
||||||
]
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
<main>
|
<main>
|
||||||
@if(isLoggedIn()){
|
@if(loggedIn()){
|
||||||
<app-contact></app-contact>
|
<app-contact></app-contact>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
|
|
||||||
@if(isLoggedIn()){
|
@if(loggedIn()){
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
<ul class="navbar-list">
|
<ul class="navbar-list">
|
||||||
<li class="navbar-item">
|
<li class="navbar-item">
|
||||||
|
|||||||
@ -10,12 +10,12 @@ import { AuthService } from '../../auth/auth.service';
|
|||||||
})
|
})
|
||||||
export class AdminLayout {
|
export class AdminLayout {
|
||||||
authSvc = inject(AuthService);
|
authSvc = inject(AuthService);
|
||||||
isLoggedIn = signal(false);
|
loggedIn = signal(false);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.authSvc.tokenReady$.subscribe(ready => {
|
this.loggedIn.set(this.authSvc.currentToken !== null);
|
||||||
if (ready) {
|
this.authSvc.accessTokenSub.subscribe(token => {
|
||||||
this.isLoggedIn.set(!!this.authSvc.getAccessToken());
|
this.loggedIn.set(token !== null);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
console.log("AdminLayout constructed");
|
console.log("AdminLayout constructed");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,5 +2,7 @@ import { bootstrapApplication } from '@angular/platform-browser';
|
|||||||
import { appConfig } from './app/app.config';
|
import { appConfig } from './app/app.config';
|
||||||
import { App } from './app/app';
|
import { App } from './app/app';
|
||||||
|
|
||||||
|
console.log('🌍 bootstrapApplication called', { time: Date.now() });
|
||||||
|
|
||||||
bootstrapApplication(App, appConfig)
|
bootstrapApplication(App, appConfig)
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => console.error(err));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user