arch(sprint1): move UI env reads behind runtime facade

This commit is contained in:
sdarbinyan
2026-07-03 01:58:08 +04:00
parent 3cbb28a116
commit ae258382e1
9 changed files with 144 additions and 24 deletions

View File

@@ -1,7 +1,7 @@
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
import { Location } from '@angular/common';
import { environment } from '../../../environments/environment';
import { TranslateService } from '../../i18n/translate.service';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
@Component({
selector: 'app-back-button',
@@ -62,9 +62,13 @@ import { TranslateService } from '../../i18n/translate.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BackButtonComponent {
isnovo = environment.theme === 'novo';
private readonly uiRuntime = inject(UiRuntimeFacade);
i18n = inject(TranslateService);
get isnovo(): boolean {
return this.uiRuntime.isNovo();
}
constructor(private location: Location) {}
goBack(): void {

View File

@@ -1,9 +1,9 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { RouterLink } from '@angular/router';
import { environment } from '../../../environments/environment';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { LogoComponent } from '../logo/logo.component';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
@Component({
selector: 'app-footer',
@@ -13,9 +13,23 @@ import { LogoComponent } from '../logo/logo.component';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FooterComponent {
constructor(private readonly uiRuntime: UiRuntimeFacade) {}
currentYear = new Date().getFullYear();
brandName = environment.brandName;
contactEmail = environment.contactEmail;
isnovo = environment.theme === 'novo';
islavero = environment.theme === 'lavero';
get brandName(): string {
return this.uiRuntime.brandName();
}
get contactEmail(): string {
return this.uiRuntime.contactEmail();
}
get isnovo(): boolean {
return this.uiRuntime.isNovo();
}
get islavero(): boolean {
return this.uiRuntime.isLavero();
}
}

View File

@@ -2,12 +2,12 @@ import { Component, ChangeDetectionStrategy, Renderer2, inject, DOCUMENT } from
import { Router, RouterLink, RouterLinkActive } from '@angular/router';
import { CartService } from '../../services/cart.service';
import { LanguageService } from '../../services/language.service';
import { environment } from '../../../environments/environment';
import { LogoComponent } from '../logo/logo.component';
import { LanguageSelectorComponent } from '../language-selector/language-selector.component';
import { RegionSelectorComponent } from '../region-selector/region-selector.component';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
@Component({
selector: 'app-header',
@@ -20,19 +20,29 @@ export class HeaderComponent {
cartItemCount;
cartTotal;
menuOpen = false;
brandName = environment.brandFullName;
logo = environment.logo;
isnovo = environment.theme === 'novo';
private renderer = inject(Renderer2);
private document = inject(DOCUMENT);
private langService = inject(LanguageService);
private uiRuntime = inject(UiRuntimeFacade);
constructor(private cartService: CartService, private router: Router) {
this.cartItemCount = this.cartService.itemCount;
this.cartTotal = this.cartService.totalPrice;
}
get brandName(): string {
return this.uiRuntime.brandFullName();
}
get logo(): string {
return this.uiRuntime.logoUrl();
}
get isnovo(): boolean {
return this.uiRuntime.isNovo();
}
get homeUrl(): string {
return `/${this.langService.currentLanguage()}`;
}

View File

@@ -6,11 +6,11 @@ import { ButtonModule } from 'primeng/button';
import { TagModule } from 'primeng/tag';
import { ApiService, CartService } from '../../services';
import { Item } from '../../models';
import { environment } from '../../../environments/environment';
import { getDiscountedPrice, getMainImage, getBadgeClass, getTranslatedField } from '../../utils/item.utils';
import { LanguageService } from '../../services/language.service';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
@Component({
selector: 'app-items-carousel',
@@ -22,7 +22,7 @@ import { TranslatePipe } from '../../i18n/translate.pipe';
export class ItemsCarouselComponent implements OnInit {
products = signal<Item[]>([]);
loading = signal(true);
isnovo = environment.theme === 'novo';
private readonly uiRuntime = inject(UiRuntimeFacade);
responsiveOptions: { breakpoint: string; numVisible: number; numScroll: number }[] | undefined;
@@ -102,6 +102,7 @@ export class ItemsCarouselComponent implements OnInit {
readonly getBadgeClass = getBadgeClass;
private langService = inject(LanguageService);
get isnovo(): boolean { return this.uiRuntime.isNovo(); }
itemName(product: Item): string { return getTranslatedField(product, 'name', this.langService.currentLanguage()); }
addToCart(event: Event, item: Item): void {

View File

@@ -1,5 +1,5 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { environment } from '../../../environments/environment';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
@Component({
selector: 'app-logo',
@@ -17,6 +17,13 @@ import { environment } from '../../../environments/environment';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LogoComponent {
brandName = environment.brandName;
logoPath = (environment as any).logo ?? `/assets/images/${environment.theme}-logo.svg`;
constructor(private readonly uiRuntime: UiRuntimeFacade) {}
get brandName(): string {
return this.uiRuntime.brandName();
}
get logoPath(): string {
return this.uiRuntime.logoUrl();
}
}

View File

@@ -0,0 +1,65 @@
import { Injectable, signal } from '@angular/core';
import { take } from 'rxjs/operators';
import { ConfigService } from '../../core/config/config.service';
import { environment } from '../../../environments/environment';
interface UiRuntimeState {
brandName: string;
brandFullName: string;
logoUrl: string;
contactEmail: string;
themeId: string;
}
@Injectable({ providedIn: 'root' })
export class UiRuntimeFacade {
private readonly state = signal<UiRuntimeState>({
brandName: environment.brandName,
brandFullName: environment.brandFullName,
logoUrl: environment.logo,
contactEmail: environment.contactEmail,
themeId: environment.theme
});
constructor(private readonly configService: ConfigService) {
this.configService.loadBootstrap().pipe(take(1)).subscribe({
next: (bootstrap) => {
this.state.set({
brandName: bootstrap.branding.brandName,
brandFullName: bootstrap.branding.brandName,
logoUrl: bootstrap.branding.logoUrl,
contactEmail: bootstrap.branding.supportEmail ?? environment.contactEmail,
themeId: bootstrap.theme.themeId
});
}
});
}
brandName(): string {
return this.state().brandName;
}
brandFullName(): string {
return this.state().brandFullName;
}
logoUrl(): string {
return this.state().logoUrl;
}
contactEmail(): string {
return this.state().contactEmail;
}
themeId(): string {
return this.state().themeId;
}
isNovo(): boolean {
return this.themeId().includes('novo');
}
isLavero(): boolean {
return this.themeId().includes('lavero');
}
}

View File

@@ -10,12 +10,12 @@ import { catchError, exhaustMap, take, timeout } from 'rxjs/operators';
import { DeliverySelectorComponent } from '../../components/delivery-selector/delivery-selector.component';
import { EmptyCartIconComponent } from '../../components/empty-cart-icon/empty-cart-icon.component';
import { TelegramLoginComponent } from '../../components/telegram-login/telegram-login.component';
import { environment } from '../../../environments/environment';
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { TranslateService } from '../../i18n/translate.service';
import { PAYMENT_POLL_INTERVAL_MS, PAYMENT_MAX_CHECKS, PAYMENT_TIMEOUT_CLOSE_MS, LINK_COPIED_DURATION_MS } from '../../config/constants';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
type PaymentMethod = 'qr' | 'card';
@@ -35,10 +35,14 @@ export class CartComponent implements OnDestroy {
hasDeliveryPrice;
allRequiredDeliveriesSelected;
termsAccepted = false;
isnovo = environment.theme === 'novo';
private readonly uiRuntime = inject(UiRuntimeFacade);
private i18n = inject(TranslateService);
private authService = inject(AuthService);
get isnovo(): boolean {
return this.uiRuntime.isNovo();
}
isAuthenticated = this.authService.isAuthenticated;

View File

@@ -2,11 +2,11 @@
import { Router, RouterLink } from '@angular/router';
import { ApiService, LanguageService } from '../../services';
import { Category } from '../../models';
import { environment } from '../../../environments/environment';
import { getTranslatedCategoryName } from '../../utils/item.utils';
import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
@Component({
selector: 'app-home',
@@ -16,8 +16,13 @@ import { TranslatePipe } from '../../i18n/translate.pipe';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HomeComponent implements OnInit, OnDestroy {
brandName = environment.brandFullName;
isnovo = environment.theme === 'novo';
constructor(
private apiService: ApiService,
private router: Router,
private langService: LanguageService,
private readonly uiRuntime: UiRuntimeFacade
) {}
categories = signal<Category[]>([]);
wideCategories = signal<Set<number>>(new Set());
loading = signal(true);
@@ -53,7 +58,13 @@ export class HomeComponent implements OnInit, OnDestroy {
return cache;
});
constructor(private apiService: ApiService, private router: Router, private langService: LanguageService) {}
get brandName(): string {
return this.uiRuntime.brandFullName();
}
get isnovo(): boolean {
return this.uiRuntime.isNovo();
}
ngOnInit(): void {
this.loadCategories();

View File

@@ -7,12 +7,12 @@ import { AuthService } from '../../services/auth.service';
import { Item, ItemDetail, DescriptionField, DeliveryOption } from '../../models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
import { environment } from '../../../environments/environment';
import { SecurityContext } from '@angular/core';
import { getStockStatus, getBadgeClass, getTranslatedField } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { TranslateService } from '../../i18n/translate.service';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
@Component({
selector: 'app-item-detail',
@@ -26,7 +26,11 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
selectedPhotoIndex = signal(0);
loading = signal(true);
error = signal<string | null>(null);
isnovo = environment.theme === 'novo';
private readonly uiRuntime = inject(UiRuntimeFacade);
get isnovo(): boolean {
return this.uiRuntime.isNovo();
}
// Variant selection
selectedColour = signal<string | null>(null);