feat(cms): add static pages module
This commit is contained in:
@@ -16,6 +16,7 @@ export interface FooterResolvedItem {
|
||||
id: string;
|
||||
label: string;
|
||||
route: string;
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
export interface FooterResolvedGroup {
|
||||
@@ -62,33 +63,17 @@ export class FooterResolverService {
|
||||
private resolveFooterGroups(bootstrap: BootstrapConfig): FooterResolvedGroup[] {
|
||||
const footer = bootstrap.navigation?.footer ?? [];
|
||||
const lang = this.languageService.currentLanguage();
|
||||
const legalKeys = bootstrap.footer?.legalPageKeys ?? [];
|
||||
const legalItems = legalKeys
|
||||
.map(key => {
|
||||
const page = this.staticPageResolver.resolveByKeyFromBootstrap(bootstrap, key, lang);
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `legal-${key}`,
|
||||
label: page.title,
|
||||
route: page.route
|
||||
} as FooterResolvedItem;
|
||||
})
|
||||
.filter((item): item is FooterResolvedItem => item != null);
|
||||
const contentPages = this.resolveBootstrapStaticPages(bootstrap, lang);
|
||||
|
||||
if (this.isGroupedFooter(footer)) {
|
||||
const groups = footer
|
||||
.map((group, index) => this.resolveGroupFromConfig(group, bootstrap, lang, index))
|
||||
.filter((group): group is FooterResolvedGroup => group != null && group.items.length > 0);
|
||||
|
||||
if (legalItems.length > 0) {
|
||||
groups.push({
|
||||
id: 'footer-group-legal',
|
||||
title: 'Legal',
|
||||
items: legalItems
|
||||
});
|
||||
for (const group of contentPages) {
|
||||
if (!groups.some(existing => existing.id === group.id)) {
|
||||
groups.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
@@ -99,9 +84,7 @@ export class FooterResolverService {
|
||||
.filter((item): item is FooterResolvedItem => item != null);
|
||||
|
||||
if (!items.length) {
|
||||
return legalItems.length
|
||||
? [{ id: 'footer-group-legal', title: 'Legal', items: legalItems }]
|
||||
: [];
|
||||
return contentPages;
|
||||
}
|
||||
|
||||
const groups: FooterResolvedGroup[] = [{
|
||||
@@ -110,15 +93,44 @@ export class FooterResolverService {
|
||||
items
|
||||
}];
|
||||
|
||||
if (legalItems.length > 0) {
|
||||
groups.push({
|
||||
id: 'footer-group-legal',
|
||||
title: 'Legal',
|
||||
items: legalItems
|
||||
groups.push(...contentPages);
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
private resolveBootstrapStaticPages(bootstrap: BootstrapConfig, lang: string): FooterResolvedGroup[] {
|
||||
const rawPages = Object.values(bootstrap.staticPages ?? {}) as any[];
|
||||
const pages = rawPages
|
||||
.filter(page => page.showInFooter !== false)
|
||||
.map(page => ({ raw: page, resolved: this.staticPageResolver.resolveByKeyFromBootstrap(bootstrap, page.id ?? '', lang) }))
|
||||
.filter(entry => entry.resolved !== null);
|
||||
|
||||
const groups = new Map<string, FooterResolvedGroup>();
|
||||
|
||||
for (const page of pages) {
|
||||
const raw = page.raw;
|
||||
const resolved = page.resolved!;
|
||||
const groupTitle = raw?.footerGroup || 'Legal';
|
||||
const groupId = `footer-group-${String(groupTitle).toLowerCase().replace(/\s+/g, '-')}`;
|
||||
if (!groups.has(groupId)) {
|
||||
groups.set(groupId, { id: groupId, title: groupTitle, items: [] });
|
||||
}
|
||||
groups.get(groupId)!.items.push({ id: resolved.id, label: resolved.title, route: resolved.route });
|
||||
}
|
||||
|
||||
const socialLinks = bootstrap.footer?.socialLinks ?? [];
|
||||
if (socialLinks.length > 0) {
|
||||
groups.set('footer-group-social', {
|
||||
id: 'footer-group-social',
|
||||
title: 'Social',
|
||||
items: socialLinks.map(link => ({ id: link.id, label: link.label, route: link.url, external: true }))
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
return [...groups.values()].map(group => ({
|
||||
...group,
|
||||
items: group.items.sort((left, right) => left.label.localeCompare(right.label))
|
||||
}));
|
||||
}
|
||||
|
||||
private resolveGroupFromConfig(
|
||||
@@ -213,14 +225,8 @@ export class FooterResolverService {
|
||||
}
|
||||
|
||||
private resolvePaymentIcons(footerConfig: FooterConfig | undefined): FooterPaymentIcon[] {
|
||||
const fallback: FooterPaymentIcon[] = [
|
||||
{ src: '/assets/images/mir-logo.svg', alt: 'MIR', width: 40, height: 28 },
|
||||
{ src: '/assets/images/visa-logo.svg', alt: 'Visa', width: 40, height: 28 },
|
||||
{ src: '/assets/images/mastercard-logo.svg', alt: 'Mastercard', width: 40, height: 28 }
|
||||
];
|
||||
|
||||
if (!footerConfig?.paymentIcons?.length) {
|
||||
return fallback;
|
||||
return [];
|
||||
}
|
||||
|
||||
return footerConfig.paymentIcons
|
||||
|
||||
@@ -2,14 +2,16 @@ import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ConfigService } from './config.service';
|
||||
import { BootstrapConfig, LegacyStaticPageConfig, LocalizedHtmlContent, LocalizedTextContent, ResolvedStaticPage, StaticPageConfig } from '../../shared/models/config';
|
||||
import { BootstrapConfig, LocalizedTextContent, ResolvedStaticPage } from '../../shared/models/config';
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
import { ContentPageService } from '../../features/content-management/services/content-page.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class StaticPageResolverService {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly languageService: LanguageService
|
||||
private readonly languageService: LanguageService,
|
||||
private readonly contentPageService: ContentPageService
|
||||
) {}
|
||||
|
||||
resolveByKey(key: string, lang?: string): Observable<ResolvedStaticPage | null> {
|
||||
@@ -25,40 +27,11 @@ export class StaticPageResolverService {
|
||||
}
|
||||
|
||||
resolveByKeyFromBootstrap(bootstrap: BootstrapConfig, key: string, lang: string): ResolvedStaticPage | null {
|
||||
const page = this.getPageByKey(bootstrap, key);
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const route = this.normalizeRoute(page.route || `/page/${key}`);
|
||||
const title = this.resolveLocalizedText(page.title, lang, key);
|
||||
const html = this.resolveLocalizedHtml(page.content, lang);
|
||||
|
||||
return {
|
||||
key,
|
||||
route,
|
||||
title,
|
||||
html
|
||||
};
|
||||
return this.contentPageService.resolvePage(bootstrap, key, lang);
|
||||
}
|
||||
|
||||
resolveByRouteFromBootstrap(bootstrap: BootstrapConfig, route: string, lang: string): ResolvedStaticPage | null {
|
||||
const normalized = this.normalizeRoute(route);
|
||||
const registry = this.getRegistry(bootstrap);
|
||||
|
||||
for (const [key, page] of Object.entries(registry)) {
|
||||
const routePath = this.normalizeRoute(page.route || `/page/${key}`);
|
||||
if (routePath === normalized) {
|
||||
return {
|
||||
key,
|
||||
route: routePath,
|
||||
title: this.resolveLocalizedText(page.title, lang, key),
|
||||
html: this.resolveLocalizedHtml(page.content, lang)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return this.contentPageService.resolvePage(bootstrap, this.normalizeRoute(route).replace(/^\//, ''), lang);
|
||||
}
|
||||
|
||||
resolveFooterTitle(groupTitle: string | LocalizedTextContent | undefined, lang: string): string {
|
||||
@@ -73,65 +46,6 @@ export class StaticPageResolverService {
|
||||
return this.resolveLocalizedText(groupTitle, lang, '');
|
||||
}
|
||||
|
||||
private getRegistry(bootstrap: BootstrapConfig): Record<string, StaticPageConfig> {
|
||||
const raw = bootstrap.staticPages;
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (Array.isArray(raw)) {
|
||||
return raw.reduce<Record<string, StaticPageConfig>>((acc, item) => {
|
||||
const legacy = item as LegacyStaticPageConfig;
|
||||
const key = legacy.key;
|
||||
if (!key) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const route = typeof legacy.route === 'string' ? legacy.route : legacy.route?.path ?? `/page/${key}`;
|
||||
|
||||
let titleMap: LocalizedTextContent;
|
||||
if (typeof legacy.title === 'string') {
|
||||
titleMap = { en: legacy.title };
|
||||
} else {
|
||||
titleMap = legacy.title ?? { en: key };
|
||||
}
|
||||
|
||||
let contentMap: LocalizedHtmlContent;
|
||||
if (typeof legacy.content === 'string') {
|
||||
contentMap = { en: legacy.content };
|
||||
} else if (legacy.content && typeof legacy.content === 'object' && 'value' in legacy.content) {
|
||||
contentMap = { en: legacy.content.value ?? '' };
|
||||
} else {
|
||||
contentMap = (legacy.content as LocalizedHtmlContent) ?? { en: '' };
|
||||
}
|
||||
|
||||
acc[key] = {
|
||||
route,
|
||||
title: titleMap,
|
||||
content: contentMap,
|
||||
visible: legacy.visible
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
private getPageByKey(bootstrap: BootstrapConfig, key: string): StaticPageConfig | null {
|
||||
const registry = this.getRegistry(bootstrap);
|
||||
const page = registry[key];
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (page.visible === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
private resolveLocalizedText(text: LocalizedTextContent | undefined, lang: string, fallback: string): string {
|
||||
if (!text) {
|
||||
return fallback;
|
||||
@@ -140,14 +54,6 @@ export class StaticPageResolverService {
|
||||
return text[lang] ?? text['en'] ?? Object.values(text)[0] ?? fallback;
|
||||
}
|
||||
|
||||
private resolveLocalizedHtml(content: LocalizedHtmlContent | undefined, lang: string): string {
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return content[lang] ?? content['en'] ?? Object.values(content)[0] ?? '';
|
||||
}
|
||||
|
||||
private normalizeRoute(route: string): string {
|
||||
if (!route) {
|
||||
return '/';
|
||||
|
||||
Reference in New Issue
Block a user