init
This commit is contained in:
@@ -9,67 +9,82 @@ export const MENU_ITEMS = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'انبارها',
|
||||
icon: 'pi pi-fw pi-box',
|
||||
items: [
|
||||
{
|
||||
label: 'لیست انبارها',
|
||||
icon: 'pi pi-fw pi-list',
|
||||
routerLink: ['/inventories'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'کالاها',
|
||||
icon: 'pi pi-fw pi-tags',
|
||||
items: [
|
||||
{
|
||||
label: 'لیست کالاها',
|
||||
icon: 'pi pi-fw pi-list',
|
||||
routerLink: ['/products'],
|
||||
},
|
||||
{
|
||||
label: 'برندهای کالا',
|
||||
icon: 'pi pi-fw pi-tags',
|
||||
routerLink: ['/product-brands'],
|
||||
},
|
||||
{
|
||||
label: 'دستهبندیهای کالا',
|
||||
icon: 'pi pi-fw pi-list',
|
||||
routerLink: ['/product-categories'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'تامینکنندگان',
|
||||
icon: 'pi pi-fw pi-users',
|
||||
items: [
|
||||
{
|
||||
label: 'لیست تامینکنندگان',
|
||||
icon: 'pi pi-fw pi-list',
|
||||
routerLink: ['/suppliers'],
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// label: 'انبارها',
|
||||
// icon: 'pi pi-fw pi-box',
|
||||
// items: [
|
||||
// {
|
||||
// label: 'لیست انبارها',
|
||||
// icon: 'pi pi-fw pi-list',
|
||||
// routerLink: ['/inventories'],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: 'کالاها',
|
||||
// icon: 'pi pi-fw pi-tags',
|
||||
// items: [
|
||||
// {
|
||||
// label: 'لیست کالاها',
|
||||
// icon: 'pi pi-fw pi-list',
|
||||
// routerLink: ['/products'],
|
||||
// },
|
||||
// {
|
||||
// label: 'برندهای کالا',
|
||||
// icon: 'pi pi-fw pi-tags',
|
||||
// routerLink: ['/product-brands'],
|
||||
// },
|
||||
// {
|
||||
// label: 'دستهبندیهای کالا',
|
||||
// icon: 'pi pi-fw pi-list',
|
||||
// routerLink: ['/product-categories'],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: 'تامینکنندگان',
|
||||
// icon: 'pi pi-fw pi-users',
|
||||
// items: [
|
||||
// {
|
||||
// label: 'لیست تامینکنندگان',
|
||||
// icon: 'pi pi-fw pi-list',
|
||||
// routerLink: ['/suppliers'],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
label: 'مدیریت سیستم',
|
||||
icon: 'pi pi-fw pi-cog',
|
||||
items: [
|
||||
{
|
||||
label: 'شعب بانکها',
|
||||
icon: 'pi pi-fw pi-building',
|
||||
routerLink: ['/bankBranches'],
|
||||
},
|
||||
{
|
||||
label: 'حسابهای بانکی',
|
||||
icon: 'pi pi-fw pi-credit-card',
|
||||
routerLink: ['/bankAccounts'],
|
||||
},
|
||||
// {
|
||||
// label: 'حسابهای بانکی',
|
||||
// icon: 'pi pi-fw pi-credit-card',
|
||||
// routerLink: ['/bankAccounts'],
|
||||
// },
|
||||
{
|
||||
label: 'کاربران',
|
||||
icon: 'pi pi-fw pi-user',
|
||||
routerLink: ['/users'],
|
||||
routerLink: ['/super_admin/users'],
|
||||
},
|
||||
{
|
||||
label: 'اصناف',
|
||||
icon: 'pi pi-fw pi-building',
|
||||
routerLink: ['/super_admin/guilds'],
|
||||
},
|
||||
{
|
||||
label: 'شرکای تجاری',
|
||||
icon: 'pi pi-fw pi-credit-card',
|
||||
routerLink: ['/super_admin/partners'],
|
||||
},
|
||||
{
|
||||
label: 'ارایهدهندگان',
|
||||
icon: 'pi pi-fw pi-credit-card',
|
||||
routerLink: ['/super_admin/providers'],
|
||||
},
|
||||
{
|
||||
label: 'لایسنسها',
|
||||
icon: 'pi pi-fw pi-credit-card',
|
||||
routerLink: ['/super_admin/licenses'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import { HttpInterceptorFn } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
||||
import { catchError, filter, switchMap, take } from 'rxjs/operators';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
/**
|
||||
* HTTP Interceptor for token management
|
||||
* - Adds Authorization header with Bearer token to requests
|
||||
* - Handles token refresh on 401 responses
|
||||
* - Prevents duplicate refresh token requests
|
||||
*/
|
||||
export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const authService = inject(AuthService);
|
||||
|
||||
// Skip auth for certain endpoints
|
||||
if (shouldSkipAuth(req.url)) {
|
||||
return next(req);
|
||||
}
|
||||
|
||||
const token = authService.getToken();
|
||||
|
||||
// Add token to request if available
|
||||
const authReq = token
|
||||
? req.clone({
|
||||
headers: req.headers.set('Authorization', `Bearer ${token}`),
|
||||
})
|
||||
: req;
|
||||
|
||||
return next(authReq).pipe(
|
||||
catchError((error) => {
|
||||
// Handle 401 Unauthorized - token might be expired
|
||||
if (error.status === 401 && !isRefreshTokenRequest(req.url)) {
|
||||
return handleTokenRefresh(authService, req, next);
|
||||
}
|
||||
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// Subject to track if refresh is in progress
|
||||
const isRefreshing = new BehaviorSubject<boolean>(false);
|
||||
|
||||
/**
|
||||
* Handle token refresh logic
|
||||
*/
|
||||
function handleTokenRefresh(authService: AuthService, req: any, next: any): Observable<any> {
|
||||
if (!isRefreshing.value) {
|
||||
isRefreshing.next(true);
|
||||
|
||||
const refreshToken = authService.getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
isRefreshing.next(false);
|
||||
authService.logout();
|
||||
return throwError(() => new Error('توکنی یافت نشد'));
|
||||
}
|
||||
|
||||
return authService.refreshToken().pipe(
|
||||
switchMap((response) => {
|
||||
isRefreshing.next(false);
|
||||
|
||||
// Retry original request with new token
|
||||
const newAuthReq = req.clone({
|
||||
headers: req.headers.set('Authorization', `Bearer ${response.token}`),
|
||||
});
|
||||
|
||||
return next(newAuthReq);
|
||||
}),
|
||||
catchError((refreshError) => {
|
||||
isRefreshing.next(false);
|
||||
authService.logout();
|
||||
return throwError(() => refreshError);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Wait for refresh to complete, then retry request
|
||||
return isRefreshing.pipe(
|
||||
filter((refreshing) => !refreshing),
|
||||
take(1),
|
||||
switchMap(() => {
|
||||
const token = authService.getToken();
|
||||
const newAuthReq = token
|
||||
? req.clone({
|
||||
headers: req.headers.set('Authorization', `Bearer ${token}`),
|
||||
})
|
||||
: req;
|
||||
|
||||
return next(newAuthReq);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request should skip authentication
|
||||
*/
|
||||
function shouldSkipAuth(url: string): boolean {
|
||||
const skipAuthUrls = [
|
||||
'/api/auth/login',
|
||||
'/api/auth/register',
|
||||
'/api/auth/forgot-password',
|
||||
'/api/auth/verify-email',
|
||||
'/api/public/',
|
||||
];
|
||||
|
||||
return skipAuthUrls.some((skipUrl) => url.includes(skipUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a refresh token request
|
||||
*/
|
||||
function isRefreshTokenRequest(url: string): boolean {
|
||||
return url.includes('/api/auth/refresh');
|
||||
}
|
||||
@@ -56,12 +56,12 @@ function handleTokenRefresh(authService: AuthService, req: any, next: any): Obse
|
||||
return throwError(() => new Error('توکنی یافت نشد'));
|
||||
}
|
||||
|
||||
return authService.refreshToken().pipe(
|
||||
return authService.doRefreshToken().pipe(
|
||||
switchMap((response) => {
|
||||
isRefreshing.next(false);
|
||||
|
||||
const newAuthReq = req.clone({
|
||||
headers: req.headers.set('Authorization', `Bearer ${response.token}`),
|
||||
headers: req.headers.set('Authorization', `Bearer ${response.accessToken}`),
|
||||
});
|
||||
|
||||
return next(newAuthReq);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export interface StockBalance {}
|
||||
@@ -1,9 +1,8 @@
|
||||
export enum UserRole {
|
||||
ADMIN = 'admin',
|
||||
SCHOOL = 'school',
|
||||
TEACHER = 'teacher',
|
||||
STUDENTS = 'students',
|
||||
GRADER = 'grader',
|
||||
PARTNER = 'partner',
|
||||
POS = 'pos',
|
||||
PROVIDER = 'provider',
|
||||
SUPERADMIN = 'superadmin',
|
||||
}
|
||||
|
||||
@@ -32,12 +31,31 @@ export interface Permission {
|
||||
|
||||
export interface IAuthResponse {
|
||||
// user: User;
|
||||
token: string;
|
||||
fullName: string;
|
||||
mustChangePassword: boolean;
|
||||
role: string;
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
expiresIn: number;
|
||||
account: IAuthAccountResponse;
|
||||
}
|
||||
|
||||
export interface IAuthAccountResponse {
|
||||
id: string;
|
||||
username: string;
|
||||
type: TRoles;
|
||||
status: string;
|
||||
created_at: string;
|
||||
user_id: string;
|
||||
partner_id?: string;
|
||||
business_id?: string;
|
||||
provider_id?: string;
|
||||
pos_id?: string;
|
||||
user: IAuthAccountUser;
|
||||
}
|
||||
interface IAuthAccountUser {
|
||||
id: string;
|
||||
created_at: string;
|
||||
mobile_number: string;
|
||||
national_code: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
}
|
||||
|
||||
export interface LoginCredentials {
|
||||
|
||||
@@ -9,336 +9,301 @@ import { BehaviorSubject, Observable, throwError } from 'rxjs';
|
||||
import { catchError, finalize, tap } from 'rxjs/operators';
|
||||
import { LOCAL_STORAGE_KEYS } from '../../../assets/constants';
|
||||
import {
|
||||
IAuthResponse,
|
||||
ISignupRequestPayload,
|
||||
IUserLoginInfo,
|
||||
LoginCredentials,
|
||||
Maybe,
|
||||
TRoles,
|
||||
User,
|
||||
IAuthAccountResponse,
|
||||
IAuthResponse,
|
||||
ISignupRequestPayload,
|
||||
IUserLoginInfo,
|
||||
LoginCredentials,
|
||||
Maybe,
|
||||
TRoles,
|
||||
} from '../models';
|
||||
import { CaptchaService } from './captcha.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
constructor() {
|
||||
this.initializeAuth();
|
||||
constructor() {
|
||||
this.initializeAuth();
|
||||
}
|
||||
|
||||
readonly modulesLoginRoutes = {
|
||||
// ADMIN: ADMIN_API_ROUTES.login(),
|
||||
// SCHOOL: SCHOOLS_API_ROUTES.login(),
|
||||
// TEACHER: TEACHERS_API_ROUTES.login(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
readonly modulesSignupRoutes = {
|
||||
// TEACHER: TEACHERS_API_ROUTES.signup(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
readonly modulesGetInfoRoutes = {
|
||||
// SCHOOL: SCHOOLS_API_ROUTES.me(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
readonly modulesChangeInfoRoutes = {
|
||||
// SCHOOL: SCHOOLS_API_ROUTES.editLoginInfo(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly router = inject(Router);
|
||||
private captchaService = inject(CaptchaService);
|
||||
|
||||
private readonly currentAccountSubject = new BehaviorSubject<Maybe<IAuthAccountResponse>>(null);
|
||||
private readonly isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
readonly currentUser$ = this.currentAccountSubject.asObservable();
|
||||
readonly isLoading$ = this.isLoadingSubject.asObservable();
|
||||
|
||||
// Signals for reactive state management
|
||||
// readonly currentUser = signal<Maybe<User>>(null);
|
||||
readonly currentAccount = signal<Maybe<IAuthAccountResponse>>(null);
|
||||
readonly isLoading = signal<boolean>(false);
|
||||
readonly token = signal<Maybe<string>>(null);
|
||||
readonly refreshToken = signal<Maybe<string>>(null);
|
||||
readonly isAuthenticated = computed(() => {
|
||||
return Boolean(this.token());
|
||||
});
|
||||
readonly userRole = computed(() => this.currentAccount()?.type);
|
||||
|
||||
private initializeAuth(): void {
|
||||
const token = localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
||||
|
||||
const accountData = localStorage.getItem(LOCAL_STORAGE_KEYS.USER_DATA);
|
||||
|
||||
if (token && accountData) {
|
||||
try {
|
||||
this.token.set(token);
|
||||
const account = JSON.parse(accountData) as IAuthAccountResponse;
|
||||
this.setCurrentUser(account);
|
||||
} catch (error) {
|
||||
console.error('Error parsing stored user data:', error);
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
login(credentials: LoginCredentials): Observable<IAuthResponse> {
|
||||
const { username, password, captcha } = credentials;
|
||||
this.isLoading.set(true);
|
||||
this.isLoadingSubject.next(true);
|
||||
const baseHeaders = this.captchaService.buildCaptchaHeaders({}, captcha);
|
||||
|
||||
return this.http
|
||||
.post<IAuthResponse>(
|
||||
'/api/v1/auth/login',
|
||||
{ username, password },
|
||||
{
|
||||
headers: baseHeaders,
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
this.handleAuthSuccess(response);
|
||||
return response;
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Login error:', error);
|
||||
this.isLoadingSubject.next(false);
|
||||
throw error;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.isLoading.set(false);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
signup(credentials: ISignupRequestPayload, role: TRoles): Observable<IAuthResponse> {
|
||||
this.isLoading.set(true);
|
||||
this.isLoadingSubject.next(true);
|
||||
// const baseHeaders = this.captchaService.buildCaptchaHeaders({}, captcha);
|
||||
|
||||
return this.http
|
||||
.post<IAuthResponse>(
|
||||
this.modulesSignupRoutes[role],
|
||||
credentials,
|
||||
// {
|
||||
// headers: baseHeaders,
|
||||
// },
|
||||
)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
this.handleAuthSuccess(response);
|
||||
return response;
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Signup error:', error);
|
||||
this.isLoadingSubject.next(false);
|
||||
throw error;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.isLoading.set(false);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN);
|
||||
|
||||
this.setCurrentUser(null);
|
||||
this.router.navigate(['/auth']);
|
||||
}
|
||||
|
||||
doRefreshToken(): Observable<IAuthResponse> {
|
||||
const refreshToken = this.getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
this.logout();
|
||||
return throwError(() => new Error('No refresh token available'));
|
||||
}
|
||||
|
||||
readonly modulesLoginRoutes = {
|
||||
// ADMIN: ADMIN_API_ROUTES.login(),
|
||||
// SCHOOL: SCHOOLS_API_ROUTES.login(),
|
||||
// TEACHER: TEACHERS_API_ROUTES.login(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
readonly modulesSignupRoutes = {
|
||||
// TEACHER: TEACHERS_API_ROUTES.signup(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
readonly modulesGetInfoRoutes = {
|
||||
// SCHOOL: SCHOOLS_API_ROUTES.me(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
readonly modulesChangeInfoRoutes = {
|
||||
// SCHOOL: SCHOOLS_API_ROUTES.editLoginInfo(),
|
||||
} as Record<TRoles, string>;
|
||||
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly router = inject(Router);
|
||||
private captchaService = inject(CaptchaService);
|
||||
|
||||
private readonly currentUserSubject = new BehaviorSubject<Maybe<User>>(
|
||||
null,
|
||||
return this.http.post<IAuthResponse>('/api/auth/refresh', { refreshToken }).pipe(
|
||||
tap((response) => {
|
||||
this.handleAuthSuccess(response);
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Token refresh error:', error);
|
||||
this.logout();
|
||||
return throwError(() => error);
|
||||
}),
|
||||
);
|
||||
private readonly isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
}
|
||||
|
||||
readonly currentUser$ = this.currentUserSubject.asObservable();
|
||||
readonly isLoading$ = this.isLoadingSubject.asObservable();
|
||||
|
||||
// Signals for reactive state management
|
||||
// readonly currentUser = signal<Maybe<User>>(null);
|
||||
readonly currentUser = signal<Maybe<User>>(null);
|
||||
readonly isLoading = signal<boolean>(false);
|
||||
readonly token = signal<Maybe<string>>(null);
|
||||
readonly isAuthenticated = computed(() => {
|
||||
return Boolean(this.token());
|
||||
});
|
||||
readonly userRole = computed(() => this.currentUser()?.role);
|
||||
|
||||
private initializeAuth(): void {
|
||||
const token = localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
||||
|
||||
const userData = localStorage.getItem(LOCAL_STORAGE_KEYS.USER_DATA);
|
||||
|
||||
if (token && userData) {
|
||||
try {
|
||||
this.token.set(token);
|
||||
const user = JSON.parse(userData) as User;
|
||||
this.setCurrentUser(user);
|
||||
} catch (error) {
|
||||
console.error('Error parsing stored user data:', error);
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
login(
|
||||
credentials: LoginCredentials,
|
||||
role: TRoles,
|
||||
): Observable<IAuthResponse> {
|
||||
const { username, password, captcha } = credentials;
|
||||
this.isLoading.set(true);
|
||||
this.isLoadingSubject.next(true);
|
||||
const baseHeaders = this.captchaService.buildCaptchaHeaders(
|
||||
{},
|
||||
captcha,
|
||||
);
|
||||
|
||||
return this.http
|
||||
.post<IAuthResponse>(
|
||||
this.modulesLoginRoutes[role],
|
||||
{ username, password },
|
||||
{
|
||||
headers: baseHeaders,
|
||||
},
|
||||
)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
this.handleAuthSuccess(response);
|
||||
return response;
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Login error:', error);
|
||||
this.isLoadingSubject.next(false);
|
||||
throw error;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.isLoading.set(false);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
signup(
|
||||
credentials: ISignupRequestPayload,
|
||||
role: TRoles,
|
||||
): Observable<IAuthResponse> {
|
||||
this.isLoading.set(true);
|
||||
this.isLoadingSubject.next(true);
|
||||
// const baseHeaders = this.captchaService.buildCaptchaHeaders({}, captcha);
|
||||
|
||||
return this.http
|
||||
.post<IAuthResponse>(
|
||||
this.modulesSignupRoutes[role],
|
||||
credentials,
|
||||
// {
|
||||
// headers: baseHeaders,
|
||||
// },
|
||||
)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
this.handleAuthSuccess(response);
|
||||
return response;
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Signup error:', error);
|
||||
this.isLoadingSubject.next(false);
|
||||
throw error;
|
||||
}),
|
||||
finalize(() => {
|
||||
this.isLoading.set(false);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN);
|
||||
|
||||
this.setCurrentUser(null);
|
||||
this.router.navigate(['/auth']);
|
||||
}
|
||||
|
||||
refreshToken(): Observable<IAuthResponse> {
|
||||
const refreshToken = this.getRefreshToken();
|
||||
if (!refreshToken) {
|
||||
this.logout();
|
||||
return throwError(() => new Error('No refresh token available'));
|
||||
}
|
||||
|
||||
return this.http
|
||||
.post<IAuthResponse>('/api/auth/refresh', { refreshToken })
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
this.handleAuthSuccess(response);
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Token refresh error:', error);
|
||||
this.logout();
|
||||
return throwError(() => error);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
getInfo(role: TRoles): Observable<Partial<IUserLoginInfo>> {
|
||||
// if (this.modulesGetInfoRoutes[role]) {
|
||||
// return this.http.get<any>(this.modulesGetInfoRoutes[role]).pipe(
|
||||
// map((data) => {
|
||||
// switch (role) {
|
||||
// case 'SCHOOL':
|
||||
// return this.prepareUserInfoBasedOnRole(role, data as ISchoolMeResponse);
|
||||
// default:
|
||||
// return data as Partial<IUserLoginInfo>;
|
||||
// }
|
||||
// }),
|
||||
// catchError((err) => {
|
||||
// console.error('GetInfo error:', err);
|
||||
// return throwError(() => err);
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
|
||||
return throwError(() => new Error('متاسفانه مشکلی پیش آمده'));
|
||||
}
|
||||
changeInfo(
|
||||
credentials: IUserLoginInfo,
|
||||
role: TRoles,
|
||||
): Observable<IAuthResponse> {
|
||||
return this.http.post<IAuthResponse>(
|
||||
this.modulesChangeInfoRoutes[role],
|
||||
credentials,
|
||||
);
|
||||
}
|
||||
|
||||
// prepareUserInfoBasedOnRole(role: TRoles, info: ISchoolMeResponse): Partial<IUserLoginInfo> {
|
||||
// if (role === 'SCHOOL') {
|
||||
// return {
|
||||
// username: info.username,
|
||||
// email: info.managerInfo.email,
|
||||
// mobile: info.managerInfo.mobile,
|
||||
// };
|
||||
// }
|
||||
// return {};
|
||||
getInfo(role: TRoles): Observable<Partial<IUserLoginInfo>> {
|
||||
// if (this.modulesGetInfoRoutes[role]) {
|
||||
// return this.http.get<any>(this.modulesGetInfoRoutes[role]).pipe(
|
||||
// map((data) => {
|
||||
// switch (role) {
|
||||
// case 'SCHOOL':
|
||||
// return this.prepareUserInfoBasedOnRole(role, data as ISchoolMeResponse);
|
||||
// default:
|
||||
// return data as Partial<IUserLoginInfo>;
|
||||
// }
|
||||
// }),
|
||||
// catchError((err) => {
|
||||
// console.error('GetInfo error:', err);
|
||||
// return throwError(() => err);
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get current access token
|
||||
*/
|
||||
getToken(): Maybe<string> {
|
||||
return localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
||||
return throwError(() => new Error('متاسفانه مشکلی پیش آمده'));
|
||||
}
|
||||
changeInfo(credentials: IUserLoginInfo, role: TRoles): Observable<IAuthResponse> {
|
||||
return this.http.post<IAuthResponse>(this.modulesChangeInfoRoutes[role], credentials);
|
||||
}
|
||||
|
||||
// prepareUserInfoBasedOnRole(role: TRoles, info: ISchoolMeResponse): Partial<IUserLoginInfo> {
|
||||
// if (role === 'SCHOOL') {
|
||||
// return {
|
||||
// username: info.username,
|
||||
// email: info.managerInfo.email,
|
||||
// mobile: info.managerInfo.mobile,
|
||||
// };
|
||||
// }
|
||||
// return {};
|
||||
// }
|
||||
|
||||
/**
|
||||
* Get current access token
|
||||
*/
|
||||
getToken(): Maybe<string> {
|
||||
return localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current refresh token
|
||||
*/
|
||||
getRefreshToken(): Maybe<string> {
|
||||
return localStorage.getItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token is expired (optional - requires JWT parsing)
|
||||
*/
|
||||
isTokenExpired(): boolean {
|
||||
const token = this.getToken();
|
||||
if (!token) return true;
|
||||
|
||||
try {
|
||||
// Parse JWT token to check expiration
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
return payload.exp < currentTime;
|
||||
} catch (error) {
|
||||
console.error('Error parsing token:', error);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if refresh token is expired
|
||||
*/
|
||||
isRefreshTokenExpired(): boolean {
|
||||
const refreshToken = this.getRefreshToken();
|
||||
if (!refreshToken) return true;
|
||||
|
||||
try {
|
||||
// Parse JWT refresh token to check expiration
|
||||
const payload = JSON.parse(atob(refreshToken.split('.')[1]));
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
return payload.exp < currentTime;
|
||||
} catch (error) {
|
||||
console.error('Error parsing refresh token:', error);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
hasRole(role: TRoles): boolean {
|
||||
return this.currentAccount()?.type === role;
|
||||
}
|
||||
|
||||
hasAnyRole(roles: TRoles[]): boolean {
|
||||
const currentRole = this.currentAccount()?.type;
|
||||
return currentRole ? roles.includes(currentRole) : false;
|
||||
}
|
||||
|
||||
hasPermission(permission: string): boolean {
|
||||
const user = this.currentAccount();
|
||||
if (!user) return false;
|
||||
|
||||
return true;
|
||||
// return user.permissions.some(
|
||||
// (p) => p.name === permission || `${p.resource}:${p.action}` === permission,
|
||||
// );
|
||||
}
|
||||
|
||||
canAccess(requiredRoles?: TRoles[], requiredPermissions?: string[]): boolean {
|
||||
if (!this.isAuthenticated()) return false;
|
||||
|
||||
if (requiredRoles && requiredRoles.length > 0) {
|
||||
if (!this.hasAnyRole(requiredRoles)) return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current refresh token
|
||||
*/
|
||||
getRefreshToken(): Maybe<string> {
|
||||
return localStorage.getItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN);
|
||||
if (requiredPermissions && requiredPermissions.length > 0) {
|
||||
return requiredPermissions.every((permission) => this.hasPermission(permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if token is expired (optional - requires JWT parsing)
|
||||
*/
|
||||
isTokenExpired(): boolean {
|
||||
const token = this.getToken();
|
||||
if (!token) return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse JWT token to check expiration
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
return payload.exp < currentTime;
|
||||
} catch (error) {
|
||||
console.error('Error parsing token:', error);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private handleAuthSuccess(response: IAuthResponse): void {
|
||||
localStorage.setItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN, response.accessToken);
|
||||
this.token.set(response.accessToken);
|
||||
this.refreshToken.set(response.refreshToken);
|
||||
localStorage.setItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN, response.refreshToken);
|
||||
|
||||
/**
|
||||
* Check if refresh token is expired
|
||||
*/
|
||||
isRefreshTokenExpired(): boolean {
|
||||
const refreshToken = this.getRefreshToken();
|
||||
if (!refreshToken) return true;
|
||||
this.setCurrentUser(response.account);
|
||||
this.isLoadingSubject.next(false);
|
||||
|
||||
try {
|
||||
// Parse JWT refresh token to check expiration
|
||||
const payload = JSON.parse(atob(refreshToken.split('.')[1]));
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
return payload.exp < currentTime;
|
||||
} catch (error) {
|
||||
console.error('Error parsing refresh token:', error);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Navigate based on user role
|
||||
// this.navigateByRole(response.user.role);
|
||||
}
|
||||
|
||||
hasRole(role: TRoles): boolean {
|
||||
return this.currentUser()?.role === role;
|
||||
}
|
||||
|
||||
hasAnyRole(roles: TRoles[]): boolean {
|
||||
const currentRole = this.currentUser()?.role;
|
||||
return currentRole ? roles.includes(currentRole) : false;
|
||||
}
|
||||
|
||||
hasPermission(permission: string): boolean {
|
||||
const user = this.currentUser();
|
||||
if (!user) return false;
|
||||
|
||||
return true;
|
||||
// return user.permissions.some(
|
||||
// (p) => p.name === permission || `${p.resource}:${p.action}` === permission,
|
||||
// );
|
||||
}
|
||||
|
||||
canAccess(
|
||||
requiredRoles?: TRoles[],
|
||||
requiredPermissions?: string[],
|
||||
): boolean {
|
||||
if (!this.isAuthenticated()) return false;
|
||||
|
||||
if (requiredRoles && requiredRoles.length > 0) {
|
||||
if (!this.hasAnyRole(requiredRoles)) return false;
|
||||
}
|
||||
|
||||
if (requiredPermissions && requiredPermissions.length > 0) {
|
||||
return requiredPermissions.every((permission) =>
|
||||
this.hasPermission(permission),
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleAuthSuccess(response: IAuthResponse): void {
|
||||
localStorage.setItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN, response.token);
|
||||
this.token.set(response.token);
|
||||
// localStorage.setItem('refresh_token', response.refreshToken);
|
||||
const user = {
|
||||
fullName: response.fullName,
|
||||
role: response.role as TRoles,
|
||||
};
|
||||
|
||||
localStorage.setItem(
|
||||
LOCAL_STORAGE_KEYS.USER_DATA,
|
||||
JSON.stringify(user),
|
||||
);
|
||||
|
||||
this.setCurrentUser(user);
|
||||
// this.isLoading.set(false);
|
||||
this.currentUser.set({
|
||||
fullName: response.fullName,
|
||||
role: response.role as TRoles,
|
||||
});
|
||||
this.isLoadingSubject.next(false);
|
||||
|
||||
// Navigate based on user role
|
||||
// this.navigateByRole(response.user.role);
|
||||
}
|
||||
|
||||
private setCurrentUser(user: Maybe<User>): void {
|
||||
this.currentUser.set(user);
|
||||
this.currentUserSubject.next(user);
|
||||
}
|
||||
private setCurrentUser(account: Maybe<IAuthAccountResponse>): void {
|
||||
this.currentAccount.set(account);
|
||||
this.currentAccountSubject.next(account);
|
||||
localStorage.setItem(LOCAL_STORAGE_KEYS.USER_DATA, JSON.stringify(account));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,20 @@ import { IPaginatedQuery, IResponseMetadata } from '../models/service.model';
|
||||
/**
|
||||
* Base interface for all state objects
|
||||
*/
|
||||
export interface BaseState<T = any> {
|
||||
export interface BaseState {
|
||||
loading: boolean;
|
||||
error: Maybe<string>;
|
||||
isRefreshing?: boolean;
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for entity
|
||||
*/
|
||||
export interface EntityState<T = any> extends BaseState {
|
||||
entity: Maybe<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for paginated lists
|
||||
*/
|
||||
@@ -29,7 +36,7 @@ export interface PaginatedState<T = any> extends BaseState {
|
||||
/**
|
||||
* Base interface for entity states (CRUD operations)
|
||||
*/
|
||||
export interface EntityState<T = any> extends BaseState {
|
||||
export interface EntitiesState<T = any> extends BaseState {
|
||||
entities: Record<string | number, T>;
|
||||
selectedId: Maybe<string | number>;
|
||||
ids: (string | number)[];
|
||||
@@ -158,11 +165,31 @@ export abstract class BaseStore<T extends BaseState> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity store with CRUD operations
|
||||
* Entity store
|
||||
*/
|
||||
export abstract class EntityStore<
|
||||
T,
|
||||
TState extends EntityState<T> = EntityState<T>,
|
||||
> extends BaseStore<TState> {
|
||||
// Computed selectors
|
||||
readonly entity = computed(() => this._state().entity);
|
||||
|
||||
/**
|
||||
* Set entity
|
||||
*/
|
||||
setEntity(entity: Maybe<T>): void {
|
||||
this.patchState({
|
||||
entity,
|
||||
} as Partial<TState>);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity store with CRUD operations
|
||||
*/
|
||||
export abstract class EntitiesStore<
|
||||
T,
|
||||
TState extends EntitiesState<T> = EntitiesState<T>,
|
||||
> extends BaseStore<TState> {
|
||||
// Computed selectors
|
||||
readonly entities = computed(() => this._state().entities);
|
||||
|
||||
@@ -4,13 +4,14 @@ export * from './base-store';
|
||||
// Global state management
|
||||
export * from './global/global.store';
|
||||
|
||||
// State effects and utilities
|
||||
export * from './state-effects.service';
|
||||
// // State effects and utilities
|
||||
// export * from './state-effects.service';
|
||||
|
||||
// State management interfaces
|
||||
export type {
|
||||
Action,
|
||||
BaseState,
|
||||
EntitiesState,
|
||||
EntityState,
|
||||
LoadingState,
|
||||
PaginatedState,
|
||||
|
||||
@@ -1,326 +0,0 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { combineLatest, map, shareReplay, startWith } from 'rxjs';
|
||||
import { LOCAL_STORAGE_KEYS } from 'src/assets/constants';
|
||||
import { AuthStore } from '../../modules/auth/state/auth.store';
|
||||
import { GlobalStore } from './global/global.store';
|
||||
|
||||
/**
|
||||
* State effects service for handling cross-store interactions
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateEffects {
|
||||
private readonly authStore = inject(AuthStore);
|
||||
private readonly globalStore = inject(GlobalStore);
|
||||
|
||||
constructor() {
|
||||
this.initializeEffects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all effects
|
||||
*/
|
||||
private initializeEffects(): void {
|
||||
this.setupAuthEffects();
|
||||
this.setupGlobalEffects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup authentication-related effects
|
||||
*/
|
||||
private setupAuthEffects(): void {
|
||||
// Update global notifications when auth state changes
|
||||
this.authStore.state$.subscribe((authState) => {
|
||||
// if (authState.isAuthenticated && authState.user) {
|
||||
// this.globalStore.addNotification({
|
||||
// type: 'success',
|
||||
// title: 'خوش آمدید',
|
||||
// message: `${authState.user.firstName} ${authState.user.lastName} عزیز، خوش آمدید`,
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
// Handle session expiry warnings
|
||||
this.authStore.state$.subscribe((authState) => {
|
||||
if (authState.sessionExpiry) {
|
||||
const timeUntilExpiry = authState.sessionExpiry - Date.now();
|
||||
const minutesUntilExpiry = Math.floor(timeUntilExpiry / 60000);
|
||||
|
||||
// Show warning when 5 minutes remain
|
||||
if (minutesUntilExpiry === 5) {
|
||||
this.globalStore.addNotification({
|
||||
type: 'warning',
|
||||
title: 'هشدار انقضای جلسه',
|
||||
message: 'جلسه شما در 5 دقیقه منقضی میشود',
|
||||
actions: [
|
||||
{
|
||||
label: 'تمدید جلسه',
|
||||
action: () => this.authStore.refreshAuthToken().subscribe(),
|
||||
type: 'primary',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup global effects
|
||||
*/
|
||||
private setupGlobalEffects(): void {
|
||||
// Auto-save preferences when they change
|
||||
this.globalStore.state$.subscribe((globalState) => {
|
||||
localStorage.setItem('user_preferences', JSON.stringify(globalState.preferences));
|
||||
});
|
||||
|
||||
// Handle theme changes
|
||||
this.globalStore.state$.subscribe((globalState) => {
|
||||
document.documentElement.setAttribute('data-theme', globalState.theme);
|
||||
});
|
||||
|
||||
// Handle language changes
|
||||
this.globalStore.state$.subscribe((globalState) => {
|
||||
document.documentElement.setAttribute('lang', globalState.language);
|
||||
document.documentElement.setAttribute('dir', globalState.language === 'fa' ? 'rtl' : 'ltr');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State selectors for complex cross-store queries
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateSelectors {
|
||||
private readonly authStore = inject(AuthStore);
|
||||
private readonly globalStore = inject(GlobalStore);
|
||||
|
||||
/**
|
||||
* Combined application state for UI components
|
||||
*/
|
||||
readonly appState$ = combineLatest([this.authStore.state$, this.globalStore.state$]).pipe(
|
||||
map(([authState, globalState]) => ({
|
||||
isAuthenticated: authState.isAuthenticated,
|
||||
user: authState.user,
|
||||
userRole: authState.user?.role,
|
||||
loading: authState.loading || globalState.loading,
|
||||
sidebarCollapsed: globalState.sidebarCollapsed,
|
||||
theme: globalState.theme,
|
||||
language: globalState.language,
|
||||
notifications: globalState.notifications,
|
||||
unreadCount: globalState.notifications.filter((n) => !n.read).length,
|
||||
breadcrumbs: globalState.breadcrumbs,
|
||||
isOnline: globalState.isOnline,
|
||||
})),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
/**
|
||||
* User permissions combined with role-based access
|
||||
*/
|
||||
readonly userAccess$ = this.authStore.state$.pipe(
|
||||
map((authState) => ({
|
||||
isAuthenticated: authState.isAuthenticated,
|
||||
role: authState.user?.role,
|
||||
permissions: authState.permissions,
|
||||
canAccessAdmin: authState.user?.role === 'ADMIN' || authState.user?.role === 'SUPERADMIN',
|
||||
canAccessStudent: authState.user?.role === 'STUDENTS',
|
||||
canAccessGrader: authState.user?.role === 'GRADER',
|
||||
canAccessSchools: authState.user?.role === 'SCHOOL',
|
||||
canAccessSuperAdmin: authState.user?.role === 'SUPERADMIN',
|
||||
})),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
/**
|
||||
* Navigation state for header/sidebar components
|
||||
*/
|
||||
readonly navigationState$ = combineLatest([this.authStore.state$, this.globalStore.state$]).pipe(
|
||||
map(([authState, globalState]) => ({
|
||||
user: authState.user,
|
||||
sidebarCollapsed: globalState.sidebarCollapsed,
|
||||
breadcrumbs: globalState.breadcrumbs,
|
||||
// userInitials: authState.user
|
||||
// ? `${authState.user.firstName[0]}${authState.user.lastName[0]}`
|
||||
// : '',
|
||||
// userFullName: authState.user ? `${authState.user.firstName} ${authState.user.lastName}` : '',
|
||||
})),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
/**
|
||||
* Notification state for notification components
|
||||
*/
|
||||
readonly notificationState$ = this.globalStore.state$.pipe(
|
||||
map((globalState) => ({
|
||||
notifications: globalState.notifications,
|
||||
unreadCount: globalState.notifications.filter((n) => !n.read).length,
|
||||
hasUnread: globalState.notifications.some((n) => !n.read),
|
||||
recentNotifications: globalState.notifications
|
||||
.sort((a, b) => b.timestamp - a.timestamp)
|
||||
.slice(0, 5),
|
||||
})),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
/**
|
||||
* Loading state across all stores
|
||||
*/
|
||||
readonly loadingState$ = combineLatest([this.authStore.state$, this.globalStore.state$]).pipe(
|
||||
map(([authState, globalState]) => ({
|
||||
isLoading: authState.loading || globalState.loading,
|
||||
authLoading: authState.loading,
|
||||
globalLoading: globalState.loading,
|
||||
})),
|
||||
startWith({ isLoading: false, authLoading: false, globalLoading: false }),
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* State utilities for common operations
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateUtils {
|
||||
private readonly authStore = inject(AuthStore);
|
||||
private readonly globalStore = inject(GlobalStore);
|
||||
|
||||
/**
|
||||
* Check if user can access route
|
||||
*/
|
||||
canAccessRoute(requiredRoles?: string[], requiredPermissions?: string[]): boolean {
|
||||
const authState = this.authStore.getCurrentState();
|
||||
|
||||
if (!authState.isAuthenticated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requiredRoles && requiredRoles.length > 0) {
|
||||
const userRole = authState.user?.role;
|
||||
if (!userRole || !requiredRoles.includes(userRole)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredPermissions && requiredPermissions.length > 0) {
|
||||
const hasAllPermissions = requiredPermissions.every((permission) =>
|
||||
authState.permissions.includes(permission),
|
||||
);
|
||||
if (!hasAllPermissions) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show success message
|
||||
*/
|
||||
showSuccess(title: string, message: string): void {
|
||||
this.globalStore.addNotification({
|
||||
type: 'success',
|
||||
title,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error message
|
||||
*/
|
||||
showError(title: string, message: string): void {
|
||||
this.globalStore.addNotification({
|
||||
type: 'error',
|
||||
title,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show warning message
|
||||
*/
|
||||
showWarning(title: string, message: string): void {
|
||||
this.globalStore.addNotification({
|
||||
type: 'warning',
|
||||
title,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show info message
|
||||
*/
|
||||
showInfo(title: string, message: string): void {
|
||||
this.globalStore.addNotification({
|
||||
type: 'info',
|
||||
title,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update page breadcrumbs
|
||||
*/
|
||||
setBreadcrumbs(breadcrumbs: Array<{ label: string; url?: string; icon?: string }>): void {
|
||||
this.globalStore.setBreadcrumbs(breadcrumbs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user display name
|
||||
*/
|
||||
getUserDisplayName(): string {
|
||||
const user = this.authStore.getCurrentState().user;
|
||||
return '';
|
||||
// return user ? `${user.firstName} ${user.lastName}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user initials
|
||||
*/
|
||||
getUserInitials(): string {
|
||||
const user = this.authStore.getCurrentState().user;
|
||||
return '';
|
||||
// return user ? `${user.firstName[0]}${user.lastName[0]}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is online
|
||||
*/
|
||||
isOnline(): boolean {
|
||||
return this.globalStore.getCurrentState().isOnline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current theme
|
||||
*/
|
||||
getCurrentTheme(): 'light' | 'dark' {
|
||||
return this.globalStore.getCurrentState().theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current language
|
||||
*/
|
||||
getCurrentLanguage(): 'fa' | 'en' {
|
||||
return this.globalStore.getCurrentState().language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all application state (useful for logout)
|
||||
*/
|
||||
clearAllState(): void {
|
||||
this.authStore.reset();
|
||||
this.globalStore.reset();
|
||||
|
||||
// Clear localStorage
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.REFRESH_TOKEN);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.USER_DATA);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.LAST_LOGIN_TIME);
|
||||
localStorage.removeItem(LOCAL_STORAGE_KEYS.LOGIN_ATTEMPTS);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user