Skip to content

Commit

Permalink
feat: user language need store to persistent database
Browse files Browse the repository at this point in the history
bugfix: interceptor not work when redirect user to login
  • Loading branch information
thongdanghoang committed Feb 23, 2025
1 parent e12c0aa commit eb244c1
Show file tree
Hide file tree
Showing 24 changed files with 271 additions and 77 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package green_buildings.commons.api.enums;

import green_buildings.commons.api.BaseEnum;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Arrays;

@Getter
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public enum UserLocale implements BaseEnum {
VI("vi-VN"),
EN("en-US"),
ZH("zh-CN");

private final String code;

public static UserLocale fromCode(String code) {
return Arrays.stream(values())
.filter(userLanguage -> userLanguage.getCode().equals(code))
.findFirst()
.orElseThrow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public JwtAuthenticationTokenDecorator convert(Jwt source) {
email,
enterpriseId,
StringUtils.EMPTY,
StringUtils.EMPTY, // can't initialize language from JWT
List.copyOf(authorities),
List.copyOf(buildingPermissions)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class UserContextData implements UserDetails {
private final String username;
private final UUID enterpriseId;
private final String password;
private final String language;
private final List<GrantedAuthority> authorities;
private final List<BuildingPermissionDTO> permissions;
private final transient List<BuildingPermissionDTO> permissions;
}
28 changes: 18 additions & 10 deletions sep490-frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {Subject, filter, takeUntil} from 'rxjs';
import {ThemeService} from './modules/core/services/theme.service';
import {UserService} from './services/user.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit, OnDestroy {
protected readonly destroy$: Subject<void> = new Subject<void>();
private systemThemeMediaQuery?: MediaQueryList;

constructor(
private readonly themeService: ThemeService,
private readonly translate: TranslateService
) {
this.themeService.initTheme();

// this language will be used as a fallback when a translation isn't found in the current language
this.translate.setDefaultLang('vi');

// the lang to use, if the lang isn't available, it will use the current loader to get them
this.translate.use('vi');
}
private readonly translate: TranslateService,
private readonly userService: UserService
) {}

ngOnInit(): void {
this.userService.userConfigs
.pipe(
takeUntil(this.destroy$),
filter(userConfigs => !!userConfigs)
)
.subscribe(userConfigs => {
this.translate.use(userConfigs.language.split('-')[0]);
this.themeService.setTheme(userConfigs.theme);
this.themeService.initTheme();
});
// Listen for system theme changes
this.systemThemeMediaQuery = window.matchMedia(
this.themeService.SYSTEM_COLOR_SCHEME_QUERY
Expand All @@ -35,6 +41,8 @@ export class AppComponent implements OnInit, OnDestroy {
}

ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
this.systemThemeMediaQuery?.removeEventListener(
'change',
this.handleThemeChange
Expand Down
3 changes: 0 additions & 3 deletions sep490-frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import {SidebarComponent} from './components/sidebar/sidebar.component';
import {UnauthorizedComponent} from './components/unauthorized/unauthorized.component';
import {CoreModule} from './modules/core/core.module';
import {HttpErrorHandlerInterceptor} from './modules/core/services/http-error-handler.interceptor';
import {LanguageInterceptor} from './modules/shared/interceptor/language.interceptor';
import {SharedModule} from './modules/shared/shared.module';

enum OidcScopes {
Expand Down Expand Up @@ -86,7 +85,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
autoUserInfo: true,
renewUserInfoAfterTokenRenew: true,
logLevel: environment.production ? LogLevel.Warn : LogLevel.Debug,
historyCleanupOff: true,
secureRoutes: [environment.idpApiUrl, environment.enterpriseUrl]
}
}),
Expand All @@ -110,7 +108,6 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
deps: [OidcSecurityService],
multi: true
},
{provide: HTTP_INTERCEPTORS, useClass: LanguageInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
{
provide: HTTP_INTERCEPTORS,
Expand Down
30 changes: 23 additions & 7 deletions sep490-frontend/src/app/components/header/header.component.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import {Component, OnInit} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {Observable} from 'rxjs';
import {Observable, map, takeUntil} from 'rxjs';
import {ApplicationService} from '../../modules/core/services/application.service';
import {ThemeService} from '../../modules/core/services/theme.service';
import {SubscriptionAwareComponent} from '../../modules/core/subscription-aware.component';
import {UserLanguage} from '../../modules/shared/enums/user-language.enum';
import {UserService} from '../../services/user.service';

interface Language {
display: string;
mobile: string;
key: string;
key: UserLanguage;
}

@Component({
Expand All @@ -27,6 +29,7 @@ export class HeaderComponent

constructor(
protected readonly applicationService: ApplicationService,
private readonly userService: UserService,
private readonly themeService: ThemeService,
private readonly translate: TranslateService
) {
Expand All @@ -36,15 +39,28 @@ export class HeaderComponent

ngOnInit(): void {
this.languages = [
{display: 'Tiếng Việt', mobile: 'VI', key: 'vi'},
{display: 'English', mobile: 'EN', key: 'en'},
{display: '中文(简体)', mobile: 'ZH', key: 'zh'}
{display: 'Tiếng Việt', mobile: 'VI', key: UserLanguage.VI},
{display: 'English', mobile: 'EN', key: UserLanguage.EN},
{display: '中文(简体)', mobile: 'ZH', key: UserLanguage.ZH}
];
this.selectedLanguage = this.languages[0];
this.translate.onLangChange
.pipe(
takeUntil(this.destroy$),
map(event => event.lang)
)
.subscribe(lang => {
this.selectedLanguage = this.languages?.find(
language => language.key.split('-')[0] === lang
);
});
}

protected changeLanguage(language: Language): void {
this.translate.use(language.key);
this.translate.use(language.key.split('-')[0]);
this.userService
.changeLanguage(language.key)
.pipe(takeUntil(this.destroy$))
.subscribe();
}

protected login(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {AuthorizationRoutingModule} from './authorization-routing.module';
import {AuthorizationComponent} from './authorization.component';
import {EnterpriseUserDetailsComponent} from './components/create-user/enterprise-user-details.component';
import {UsersComponent} from './components/users/users.component';
import {UserService} from './services/user.service';
import {EnterpriseUserService} from './services/enterprise-user.service';

@NgModule({
declarations: [
Expand All @@ -14,6 +14,6 @@ import {UserService} from './services/user.service';
UsersComponent
],
imports: [SharedModule, AuthorizationRoutingModule],
providers: [UserService]
providers: [EnterpriseUserService]
})
export class AuthorizationModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
BuildingPermission,
EnterpriseUserDetails
} from '../../models/enterprise-user';
import {UserService} from '../../services/user.service';
import {EnterpriseUserService} from '../../services/enterprise-user.service';
import {MultiSelectChangeEvent} from 'primeng/multiselect';
import {SelectChangeEvent} from 'primeng/select';

Expand Down Expand Up @@ -125,7 +125,7 @@ export class EnterpriseUserDetailsComponent extends AbstractFormComponent<Enterp
formBuilder: FormBuilder,
notificationService: MessageService,
translate: TranslateService,
protected userService: UserService,
protected userService: EnterpriseUserService,
private readonly router: Router,
private readonly activatedRoute: ActivatedRoute
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../../../shared/models/models';
import {ModalProvider} from '../../../shared/services/modal-provider';
import {EnterpriseUser} from '../../models/enterprise-user';
import {UserService} from '../../services/user.service';
import {EnterpriseUserService} from '../../services/enterprise-user.service';

export interface UserCriteria {
criteria: string;
Expand Down Expand Up @@ -52,7 +52,7 @@ export class UsersComponent

constructor(
protected readonly applicationService: ApplicationService,
private readonly userService: UserService,
private readonly userService: EnterpriseUserService,
private readonly messageService: MessageService,
private readonly modalProvider: ModalProvider,
private readonly translate: TranslateService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {EnterpriseUser, EnterpriseUserDetails} from '../models/enterprise-user';
import {UserCriteria} from '../components/users/users.component';

@Injectable()
export class UserService {
export class EnterpriseUserService {
constructor(private readonly httpClient: HttpClient) {}

public getUsers(
Expand Down
27 changes: 20 additions & 7 deletions sep490-frontend/src/app/modules/core/services/theme.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {definePreset} from '@primeng/themes';
import Aura from '@primeng/themes/aura';
import {PrimeNG, ThemeType} from 'primeng/config';
import {BehaviorSubject, Observable, of} from 'rxjs';
import {UserService} from '../../../services/user.service';
import {Theme} from '../../shared/models/user-configs';

const MyPreset = definePreset(Aura, {
primitive: {
Expand Down Expand Up @@ -41,14 +43,15 @@ const MyPreset = definePreset(Aura, {
export class ThemeService {
readonly LOCAL_STORAGE_KEY = 'prefers-color-scheme';
readonly TOKEN = 'my-app-dark';
readonly DARK_MODE = 'dark';
readonly LIGHT_MODE = 'light';
readonly SYSTEM_COLOR_SCHEME_QUERY = '(prefers-color-scheme: dark)';
systemPreferredColorThemeChanged: BehaviorSubject<boolean>;
systemPreferredColorTheme: ThemeType;
userPreferredColorTheme: ThemeType;

constructor(private readonly config: PrimeNG) {
constructor(
private readonly config: PrimeNG,
private readonly userService: UserService
) {
const themeOptions = {
prefix: 'p',
darkModeSelector: 'system',
Expand All @@ -73,7 +76,7 @@ export class ThemeService {
initTheme(): void {
if (this.isThemeConfigured()) {
this.config.theme.set(this.userPreferredColorTheme);
if (localStorage.getItem(this.LOCAL_STORAGE_KEY) === this.DARK_MODE) {
if (localStorage.getItem(this.LOCAL_STORAGE_KEY) === Theme[Theme.DARK]) {
document.querySelector('html')?.classList.add(this.TOKEN);
}
return;
Expand All @@ -87,7 +90,7 @@ export class ThemeService {
isDarkMode(): Observable<boolean> {
if (this.isThemeConfigured()) {
return of(
localStorage.getItem(this.LOCAL_STORAGE_KEY) === this.DARK_MODE
localStorage.getItem(this.LOCAL_STORAGE_KEY) === Theme[Theme.DARK]
);
}
return this.systemPreferredColorThemeChanged;
Expand All @@ -96,13 +99,23 @@ export class ThemeService {
toggleLightDark(): void {
this.config.theme.set(this.userPreferredColorTheme);
if (document.querySelector('html')?.classList.contains(this.TOKEN)) {
localStorage.setItem(this.LOCAL_STORAGE_KEY, this.LIGHT_MODE);
localStorage.setItem(this.LOCAL_STORAGE_KEY, Theme[Theme.LIGHT]);
this.userService.changeTheme(Theme[Theme.LIGHT]).subscribe();
} else {
localStorage.setItem(this.LOCAL_STORAGE_KEY, this.DARK_MODE);
localStorage.setItem(this.LOCAL_STORAGE_KEY, Theme[Theme.DARK]);
this.userService.changeTheme(Theme[Theme.DARK]).subscribe();
}
document.querySelector('html')?.classList.toggle(this.TOKEN);
}

setTheme(theme: keyof typeof Theme): void {
if (theme === Theme[Theme.SYSTEM]) {
localStorage.removeItem(this.LOCAL_STORAGE_KEY);
return;
}
localStorage.setItem(this.LOCAL_STORAGE_KEY, theme);
}

private isThemeConfigured(): boolean {
return !!localStorage.getItem(this.LOCAL_STORAGE_KEY);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum UserLanguage {
VI = 'vi-VN',
EN = 'en-US',
ZH = 'zh-CN'
}

This file was deleted.

12 changes: 12 additions & 0 deletions sep490-frontend/src/app/modules/shared/models/user-configs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {UserLanguage} from '../enums/user-language.enum';

export interface UserConfigs {
language: UserLanguage;
theme: keyof typeof Theme;
}

export enum Theme {
DARK,
LIGHT,
SYSTEM
}
33 changes: 33 additions & 0 deletions sep490-frontend/src/app/services/user.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {AppRoutingConstants} from '../app-routing.constant';
import {UserLanguage} from '../modules/shared/enums/user-language.enum';
import {UserConfigs} from '../modules/shared/models/user-configs';

@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private readonly httpClient: HttpClient) {}

get userConfigs(): Observable<UserConfigs> {
return this.httpClient.get<UserConfigs>(
`${AppRoutingConstants.IDP_API_URL}/user/configs`
);
}

changeLanguage(language: UserLanguage): Observable<void> {
return this.httpClient.put<void>(
`${AppRoutingConstants.IDP_API_URL}/user/language/${language}`,
{}
);
}

changeTheme(theme: string): Observable<void> {
return this.httpClient.put<void>(
`${AppRoutingConstants.IDP_API_URL}/user/theme/${theme}`,
{}
);
}
}
Loading

0 comments on commit eb244c1

Please sign in to comment.