refactor: finalize bootstrap-driven layout and widget runtime

This commit is contained in:
sdarbinyan
2026-07-05 04:17:31 +04:00
parent 6550250d13
commit c901ec1e49
19 changed files with 697 additions and 181 deletions

View File

@@ -24,15 +24,21 @@
<div class="app-footer__group">
<h4>{{ 'footer.payment' | translate }}</h4>
<div class="app-footer__payments">
<img src="/assets/images/mir-logo.svg" alt="MIR" loading="lazy" width="40" height="28" />
<img src="/assets/images/visa-logo.svg" alt="Visa" loading="lazy" width="40" height="28" />
<img src="/assets/images/mastercard-logo.svg" alt="Mastercard" loading="lazy" width="40" height="28" />
@for (icon of paymentIcons(); track icon.src) {
<img [src]="icon.src" [alt]="icon.alt" loading="lazy" [width]="icon.width" [height]="icon.height" />
}
</div>
</div>
</div>
<div class="app-footer__bottom">
<p>&copy; {{ currentYear }} {{ brandName }}. {{ 'footer.allRightsReserved' | translate }}</p>
<p>
@if (copyrightText()) {
{{ copyrightText() }}
} @else {
&copy; {{ currentYear }} {{ brandName }}. {{ 'footer.allRightsReserved' | translate }}
}
</p>
@if (contactEmail) {
<a [href]="'mailto:' + contactEmail">{{ contactEmail }}</a>
}

View File

@@ -5,22 +5,7 @@ import { TranslatePipe } from '../../i18n/translate.pipe';
import { LogoComponent } from '../logo/logo.component';
import { UiRuntimeFacade } from '../../facades/runtime/ui-runtime.facade';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { ConfigService } from '../../core/config/config.service';
import { StaticPageResolverService } from '../../core/config/static-page-resolver.service';
import { BootstrapConfig, FooterNavigationGroupConfig, NavigationConfig, NavigationItemConfig, FooterNavigationItemConfig } from '../../shared/models/config';
import { LanguageService } from '../../services/language.service';
interface FooterResolvedItem {
id: string;
label: string;
route: string;
}
interface FooterResolvedGroup {
id: string;
title: string;
items: FooterResolvedItem[];
}
import { FooterPaymentIcon, FooterResolvedGroup, FooterResolverService } from '../../core/config/footer-resolver.service';
@Component({
selector: 'app-footer',
@@ -31,19 +16,23 @@ interface FooterResolvedGroup {
})
export class FooterComponent {
readonly footerGroups = signal<FooterResolvedGroup[]>([]);
readonly paymentIcons = signal<FooterPaymentIcon[]>([]);
readonly copyrightText = signal('');
constructor(
private readonly uiRuntime: UiRuntimeFacade,
private readonly configService: ConfigService,
private readonly staticPageResolver: StaticPageResolverService,
private readonly languageService: LanguageService
private readonly footerResolver: FooterResolverService
) {
this.configService.loadBootstrap().pipe(take(1)).subscribe({
next: bootstrap => {
this.footerGroups.set(this.resolveFooterGroups(bootstrap));
this.footerResolver.resolveFooterModel().pipe(take(1)).subscribe({
next: model => {
this.footerGroups.set(model.groups);
this.paymentIcons.set(model.paymentIcons);
this.copyrightText.set(model.copyrightText);
},
error: () => {
this.footerGroups.set([]);
this.paymentIcons.set([]);
this.copyrightText.set('');
}
});
}
@@ -57,120 +46,4 @@ export class FooterComponent {
get contactEmail(): string {
return this.uiRuntime.contactEmail();
}
private resolveFooterGroups(bootstrap: BootstrapConfig): FooterResolvedGroup[] {
const footer = bootstrap.navigation?.footer ?? [];
const lang = this.languageService.currentLanguage();
if (this.isGroupedFooter(footer)) {
return footer
.map((group, index) => this.resolveGroupFromConfig(group, bootstrap, lang, index))
.filter((group): group is FooterResolvedGroup => group != null && group.items.length > 0);
}
const items = (footer as NavigationItemConfig[])
.map(item => this.resolveLegacyItem(item, bootstrap, lang))
.filter((item): item is FooterResolvedItem => item != null);
if (!items.length) {
return [];
}
return [{
id: 'footer-group-default',
title: '',
items
}];
}
private resolveGroupFromConfig(
group: FooterNavigationGroupConfig,
bootstrap: BootstrapConfig,
lang: string,
index: number
): FooterResolvedGroup | null {
const items = (group.items ?? [])
.map(item => this.resolveGroupItem(item, bootstrap, lang))
.filter((item): item is FooterResolvedItem => item != null);
if (!items.length) {
return null;
}
return {
id: `footer-group-${index}`,
title: this.staticPageResolver.resolveFooterTitle(group.groupTitle, lang),
items
};
}
private resolveGroupItem(
item: FooterNavigationItemConfig,
bootstrap: BootstrapConfig,
lang: string
): FooterResolvedItem | null {
if (item.visible === false) {
return null;
}
if (item.type === 'staticPage' && item.key) {
const staticPage = this.staticPageResolver.resolveByKeyFromBootstrap(bootstrap, item.key, lang);
if (!staticPage) {
return null;
}
return {
id: `static-${item.key}`,
label: staticPage.title,
route: staticPage.route
};
}
const label = typeof item.label === 'string'
? item.label
: this.staticPageResolver.resolveFooterTitle(item.label, lang);
if (!item.route || !label) {
return null;
}
return {
id: `${item.type}-${label}`,
label,
route: item.route
};
}
private resolveLegacyItem(item: NavigationItemConfig, bootstrap: BootstrapConfig, lang: string): FooterResolvedItem | null {
if (item.visible === false) {
return null;
}
if (item.type === 'staticPage' && item.key) {
const staticPage = this.staticPageResolver.resolveByKeyFromBootstrap(bootstrap, item.key, lang);
if (!staticPage) {
return null;
}
return {
id: item.id,
label: staticPage.title,
route: staticPage.route
};
}
if (!item.route) {
return null;
}
return {
id: item.id,
label: item.labelKey ?? item.id,
route: item.route
};
}
private isGroupedFooter(footer: NavigationConfig['footer']): footer is FooterNavigationGroupConfig[] {
return Array.isArray(footer) && footer.length > 0 && 'items' in footer[0];
}
}