feat: update app version and build details; refactor auth service and components for improved functionality

This commit is contained in:
2026-05-16 16:11:58 +03:30
parent 3f75d82295
commit fe09aa4931
10 changed files with 124 additions and 40 deletions
+3 -3
View File
@@ -1,9 +1,9 @@
{ {
"$schema": "./node_modules/@angular/service-worker/config/schema.json", "$schema": "./node_modules/@angular/service-worker/config/schema.json",
"appData": { "appData": {
"appVersion": "0.0.12", "appVersion": "0.0.13",
"buildDate": "2026-05-16T08:49:47.932Z", "buildDate": "2026-05-16T11:46:52.423Z",
"buildNumber": 12 "buildNumber": 13
}, },
"assetGroups": [ "assetGroups": [
{ {
+34 -15
View File
@@ -6,8 +6,8 @@ import { HttpClient } from '@angular/common/http';
import { computed, inject, Injectable, signal } from '@angular/core'; import { computed, inject, Injectable, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { CookieService } from 'ngx-cookie-service'; import { CookieService } from 'ngx-cookie-service';
import { BehaviorSubject, Observable, throwError } from 'rxjs'; import { BehaviorSubject, from, Observable, throwError } from 'rxjs';
import { catchError, finalize, tap } from 'rxjs/operators'; import { catchError, finalize, switchMap, tap } from 'rxjs/operators';
import config from 'src/config'; import config from 'src/config';
import { COOKIE_KEYS, LOCAL_STORAGE_KEYS } from '../../../assets/constants'; import { COOKIE_KEYS, LOCAL_STORAGE_KEYS } from '../../../assets/constants';
import { import {
@@ -82,11 +82,14 @@ export class AuthService {
}, },
) )
.pipe( .pipe(
tap((response) => { switchMap((response) =>
this.handleAuthSuccess(response); from(
this.refreshCookies(); this.handleAuthSuccess(response).then(() => {
return response; this.refreshCookies();
}), return response;
}),
),
),
catchError((error) => { catchError((error) => {
console.error('Login error:', error); console.error('Login error:', error);
this.isLoadingSubject.next(false); this.isLoadingSubject.next(false);
@@ -98,19 +101,19 @@ export class AuthService {
); );
} }
logout(): void { async logout(): Promise<void> {
localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA); localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA);
localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN); localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN);
this.refreshCookies(); await this.refreshCookies();
this.setCurrentUser(null); await this.setCurrentUser(null);
this.router.navigate(['/auth']); this.router.navigate(['/auth']);
} }
refreshCookies() { async refreshCookies() {
this.cookieService.set(COOKIE_KEYS.POS_ID, '', new Date()); await this.cookieService.set(COOKIE_KEYS.POS_ID, '', new Date());
this.cookieService.set(COOKIE_KEYS.ACCESS_TOKEN, '', new Date()); await this.cookieService.set(COOKIE_KEYS.ACCESS_TOKEN, '', new Date());
} }
doRefreshToken(): Observable<IAuthResponse> { doRefreshToken(): Observable<IAuthResponse> {
@@ -171,6 +174,22 @@ export class AuthService {
return localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN); return localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
} }
waitForAuthReady(maxRetries = 20): Promise<boolean> {
return new Promise((resolve) => {
let retries = 0;
const check = () => {
const isReady = Boolean(this.getToken()) && Boolean(this.currentAccount());
if (isReady || retries >= maxRetries) {
resolve(isReady);
return;
}
retries += 1;
setTimeout(check, 0);
};
check();
});
}
/** /**
* Get current refresh token * Get current refresh token
*/ */
@@ -247,7 +266,7 @@ export class AuthService {
return true; return true;
} }
private handleAuthSuccess(response: IAuthResponse): void { private async handleAuthSuccess(response: IAuthResponse): Promise<void> {
localStorage.setItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN, response.accessToken); localStorage.setItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN, response.accessToken);
this.token.set(response.accessToken); this.token.set(response.accessToken);
this.refreshToken.set(response.refreshToken); this.refreshToken.set(response.refreshToken);
@@ -260,7 +279,7 @@ export class AuthService {
// this.navigateByRole(response.user.role); // this.navigateByRole(response.user.role);
} }
private setCurrentUser(account: Maybe<IAuthAccountResponse>): void { private async setCurrentUser(account: Maybe<IAuthAccountResponse>): Promise<void> {
this.currentAccount.set(account); this.currentAccount.set(account);
this.currentAccountSubject.next(account); this.currentAccountSubject.next(account);
localStorage.setItem(LOCAL_STORAGE_KEYS.USER_DATA, JSON.stringify(account)); localStorage.setItem(LOCAL_STORAGE_KEYS.USER_DATA, JSON.stringify(account));
@@ -25,13 +25,13 @@
<!-- [ngStyle]="{ transform: 'scale(1.' + pullDistance() * 10 + ')' }" --> <!-- [ngStyle]="{ transform: 'scale(1.' + pullDistance() * 10 + ')' }" -->
</div> </div>
} }
@if (isAuth()) { <!-- @if (isAuth()) {
@defer (when isAuth()) { @defer (when isAuth()) {
<router-outlet></router-outlet> <router-outlet></router-outlet>
} }
} @else { } @else { -->
<pos-pages-layout /> <pos-pages-layout />
} <!-- } -->
</div> </div>
} }
} }
@@ -0,0 +1 @@
export * from './routes/index';
@@ -0,0 +1,19 @@
import { NamedRoutes } from '@/core';
import { Routes } from '@angular/router';
export type TPosAuthRouteNames = 'login';
const baseRoute = `auth`;
export const posAuthNamedRoutes: NamedRoutes<TPosAuthRouteNames> = {
login: {
path: 'auth',
loadComponent: () => import('../../views/auth.component').then((m) => m.PosAuthComponent),
meta: {
title: 'ورود',
pagePath: () => baseRoute,
},
},
};
export const POS_AUTH_ROUTES: Routes = [posAuthNamedRoutes.login];
@@ -0,0 +1 @@
<app-auth (onSubmit)="onLoggedIn($event)" />
@@ -0,0 +1,18 @@
import { AuthComponent } from '@/modules/auth/pages/auth.component';
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'pos-auth',
templateUrl: 'auth.component.html',
imports: [AuthComponent],
})
export class PosAuthComponent {
private readonly router = inject(Router);
onLoggedIn(isReady: boolean) {
if (isReady) {
this.router.navigateByUrl('/');
}
}
}
+13 -8
View File
@@ -1,4 +1,5 @@
import { IAuthResponse, TRoles } from '@/core'; import { IAuthResponse, TRoles } from '@/core';
import { AuthService } from '@/core/services/auth.service';
import { ToastService } from '@/core/services/toast.service'; import { ToastService } from '@/core/services/toast.service';
import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core'; import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -19,10 +20,11 @@ export class AuthComponent {
@Input() role?: TRoles; @Input() role?: TRoles;
@Input() defaultStep: TSteps = 'login'; @Input() defaultStep: TSteps = 'login';
@Output() submit = new EventEmitter<Promise<boolean>>(); @Output() onSubmit = new EventEmitter<boolean>();
private readonly toastService = inject(ToastService); private readonly toastService = inject(ToastService);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly authService = inject(AuthService);
readonly logo = images.logo; readonly logo = images.logo;
readonly authVector = images.login; readonly authVector = images.login;
@@ -36,7 +38,7 @@ export class AuthComponent {
this.activeStep.set('signup'); this.activeStep.set('signup');
}; };
onLoggedIn = (data: IAuthResponse) => { onLoggedIn = async (data: IAuthResponse) => {
this.toastService.success({ text: 'شما با موفقیت وارد شدید.' }); this.toastService.success({ text: 'شما با موفقیت وارد شدید.' });
this.selectedRole.set(data.account.type.toUpperCase() as TRoles); this.selectedRole.set(data.account.type.toUpperCase() as TRoles);
@@ -46,15 +48,18 @@ export class AuthComponent {
// return; // return;
// } // }
if (this.submit.observed) { if (this.onSubmit.observed) {
return this.submit.emit(Promise.resolve(true)); const isReady = await this.authService.waitForAuthReady();
return this.onSubmit.emit(isReady);
} else {
this.redirectToDashboard();
} }
this.redirectToDashboard();
}; };
onModifyLoginInfo = () => { onModifyLoginInfo = async () => {
if (this.submit.observed) { if (this.onSubmit.observed) {
return this.submit.emit(Promise.resolve(true)); const isReady = await this.authService.waitForAuthReady();
return this.onSubmit.emit(isReady);
} }
this.redirectToDashboard(); this.redirectToDashboard();
}; };
@@ -101,9 +101,11 @@ export class LoginComponent {
next: (data) => { next: (data) => {
this.errorMessage.set(''); this.errorMessage.set('');
if (this.redirectUrl) { if (this.redirectUrl) {
this.router.navigateByUrl(this.redirectUrl.replace(/\/[^/]*$/, '')); this.navigateAfterAuth(this.redirectUrl.replace(/\/[^/]*$/, ''));
} else { } else {
this.onSuccessfullySubmit.emit(data); this.waitForAuthState().then(() => {
this.onSuccessfullySubmit.emit(data);
});
} }
}, },
error: (error) => { error: (error) => {
@@ -113,4 +115,25 @@ export class LoginComponent {
}, },
}); });
} }
private navigateAfterAuth(url: string) {
this.waitForAuthState().then(() => {
this.router.navigateByUrl(url);
});
}
private waitForAuthState(maxRetries = 10): Promise<void> {
return new Promise((resolve) => {
let retries = 0;
const check = () => {
if (this.authService.getToken() || retries >= maxRetries) {
resolve();
return;
}
retries += 1;
setTimeout(check, 0);
};
check();
});
}
} }
+6 -8
View File
@@ -1,3 +1,4 @@
import { posAuthNamedRoutes } from '@/domains/pos/modules/auth/constants';
import { POS_ROUTES } from '@/domains/pos/routes'; import { POS_ROUTES } from '@/domains/pos/routes';
import { Notfound } from '@/pages/notfound/notfound.component'; import { Notfound } from '@/pages/notfound/notfound.component';
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
@@ -7,14 +8,11 @@ export const appRoutes: Routes = [
path: '', path: '',
loadComponent: () => loadComponent: () =>
import('@/domains/pos/layouts/layout.component').then((m) => m.PosLayoutComponent), import('@/domains/pos/layouts/layout.component').then((m) => m.PosLayoutComponent),
children: [ children: [POS_ROUTES],
POS_ROUTES, },
{ {
path: 'auth', ...posAuthNamedRoutes.login,
loadComponent: () => path: 'auth',
import('@/modules/auth/pages/auth.component').then((m) => m.AuthComponent),
},
],
}, },
{ path: 'notfound', component: Notfound }, { path: 'notfound', component: Notfound },
{ path: '**', redirectTo: '/notfound' }, { path: '**', redirectTo: '/notfound' },