2026-07-10 13:52:01 +04:00
|
|
|
import { ChangeDetectionStrategy, Component, DestroyRef, SecurityContext, effect, inject, signal } from '@angular/core';
|
2026-07-05 03:37:35 +04:00
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
2026-07-10 13:52:01 +04:00
|
|
|
import { DomSanitizer, Meta, SafeHtml, Title } from '@angular/platform-browser';
|
|
|
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
2026-07-05 03:37:35 +04:00
|
|
|
import { StaticPageResolverService } from '../../core/config/static-page-resolver.service';
|
|
|
|
|
import { LanguageService } from '../../services/language.service';
|
|
|
|
|
import { TranslatePipe } from '../../i18n/translate.pipe';
|
fix(storefront): composition audit fixes for cart, catalog, product, compare, wishlist, static pages
- Replace hand-rolled loading/error/empty markup with shared app-skeleton,
app-empty-state, and app-button across catalog, product details, cart,
compare, wishlist, and the public static-page renderer
- Fix hardcoded hex colors that bypassed theme CSS variables (catalog,
product details), restoring multi-tenant theme correctness
- Remove ~1100 lines of dead "alt" cart theme CSS (never applied by the
template) from cart.component.scss, bringing it back under the 40kB
build budget (89.49kB -> 59.39kB cart-component chunk)
- Swap legacy global .btn/.btn-ghost/.btn-primary classes for app-button
in compare and wishlist empty/toolbar actions
2026-07-23 10:37:06 +04:00
|
|
|
import { EmptyStateComponent } from '../../shared/ui/empty-state/empty-state.component';
|
|
|
|
|
import { SkeletonComponent } from '../../shared/ui/skeleton/skeleton.component';
|
|
|
|
|
import { ButtonComponent } from '../../shared/ui/button/button.component';
|
2026-07-05 03:37:35 +04:00
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'app-static-page',
|
|
|
|
|
standalone: true,
|
fix(storefront): composition audit fixes for cart, catalog, product, compare, wishlist, static pages
- Replace hand-rolled loading/error/empty markup with shared app-skeleton,
app-empty-state, and app-button across catalog, product details, cart,
compare, wishlist, and the public static-page renderer
- Fix hardcoded hex colors that bypassed theme CSS variables (catalog,
product details), restoring multi-tenant theme correctness
- Remove ~1100 lines of dead "alt" cart theme CSS (never applied by the
template) from cart.component.scss, bringing it back under the 40kB
build budget (89.49kB -> 59.39kB cart-component chunk)
- Swap legacy global .btn/.btn-ghost/.btn-primary classes for app-button
in compare and wishlist empty/toolbar actions
2026-07-23 10:37:06 +04:00
|
|
|
imports: [CommonModule, RouterLink, TranslatePipe, EmptyStateComponent, SkeletonComponent, ButtonComponent],
|
2026-07-05 03:37:35 +04:00
|
|
|
templateUrl: './static-page.component.html',
|
|
|
|
|
styleUrls: ['./static-page.component.scss'],
|
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
|
|
|
|
})
|
|
|
|
|
export class StaticPageComponent {
|
|
|
|
|
private readonly route = inject(ActivatedRoute);
|
2026-07-10 13:52:01 +04:00
|
|
|
private readonly destroyRef = inject(DestroyRef);
|
2026-07-05 03:37:35 +04:00
|
|
|
private readonly sanitizer = inject(DomSanitizer);
|
|
|
|
|
private readonly staticPageResolver = inject(StaticPageResolverService);
|
|
|
|
|
private readonly languageService = inject(LanguageService);
|
2026-07-10 13:52:01 +04:00
|
|
|
private readonly titleService = inject(Title);
|
|
|
|
|
private readonly meta = inject(Meta);
|
2026-07-05 03:37:35 +04:00
|
|
|
|
|
|
|
|
readonly loading = signal(true);
|
|
|
|
|
readonly notFound = signal(false);
|
2026-08-13 08:56:25 +04:00
|
|
|
readonly error = signal(false);
|
2026-07-05 03:37:35 +04:00
|
|
|
readonly title = signal('');
|
2026-07-05 03:41:13 +04:00
|
|
|
readonly homeRoute = signal('');
|
2026-07-10 13:52:01 +04:00
|
|
|
readonly dir = signal<'ltr' | 'rtl'>('ltr');
|
2026-07-05 03:37:35 +04:00
|
|
|
readonly safeHtml = signal<SafeHtml>(this.sanitizer.bypassSecurityTrustHtml(''));
|
|
|
|
|
|
2026-07-10 13:52:01 +04:00
|
|
|
private lastKey: string | null = null;
|
|
|
|
|
private lastPath: string | null = null;
|
|
|
|
|
|
2026-07-05 03:37:35 +04:00
|
|
|
constructor() {
|
2026-07-10 13:52:01 +04:00
|
|
|
effect(() => {
|
|
|
|
|
const lang = this.languageService.currentLanguage();
|
|
|
|
|
this.homeRoute.set(`/${lang}`);
|
|
|
|
|
this.dir.set(['ar', 'fa', 'he', 'ur'].includes(lang) ? 'rtl' : 'ltr');
|
|
|
|
|
|
|
|
|
|
if (this.lastKey) {
|
|
|
|
|
this.loadByKey(this.lastKey);
|
|
|
|
|
} else if (this.lastPath) {
|
|
|
|
|
this.loadByPath(this.lastPath);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-07-05 03:41:13 +04:00
|
|
|
|
2026-07-10 13:52:01 +04:00
|
|
|
this.route.paramMap.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(params => {
|
2026-07-05 03:37:35 +04:00
|
|
|
const keyFromParam = params.get('key');
|
|
|
|
|
const staticPath = params.get('staticPath');
|
|
|
|
|
|
|
|
|
|
if (keyFromParam) {
|
2026-07-10 13:52:01 +04:00
|
|
|
this.lastKey = keyFromParam;
|
|
|
|
|
this.lastPath = null;
|
2026-07-05 03:37:35 +04:00
|
|
|
this.loadByKey(keyFromParam);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (staticPath) {
|
2026-07-10 13:52:01 +04:00
|
|
|
this.lastKey = null;
|
|
|
|
|
this.lastPath = `/${staticPath}`;
|
2026-07-05 03:37:35 +04:00
|
|
|
this.loadByPath(`/${staticPath}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.notFound.set(true);
|
|
|
|
|
this.loading.set(false);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private loadByKey(key: string): void {
|
|
|
|
|
this.loading.set(true);
|
|
|
|
|
this.notFound.set(false);
|
2026-08-13 08:56:25 +04:00
|
|
|
this.error.set(false);
|
2026-07-05 03:37:35 +04:00
|
|
|
|
2026-08-13 08:56:25 +04:00
|
|
|
this.staticPageResolver.resolveByKey(key, this.languageService.currentLanguage()).subscribe({
|
|
|
|
|
next: page => this.applyPage(page?.title ?? '', page?.html ?? '', !page),
|
|
|
|
|
error: () => this.applyError()
|
2026-07-05 03:37:35 +04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private loadByPath(path: string): void {
|
|
|
|
|
this.loading.set(true);
|
|
|
|
|
this.notFound.set(false);
|
2026-08-13 08:56:25 +04:00
|
|
|
this.error.set(false);
|
2026-07-05 03:37:35 +04:00
|
|
|
|
2026-08-13 08:56:25 +04:00
|
|
|
this.staticPageResolver.resolveByRoute(path, this.languageService.currentLanguage()).subscribe({
|
|
|
|
|
next: page => this.applyPage(page?.title ?? '', page?.html ?? '', !page),
|
|
|
|
|
error: () => this.applyError()
|
2026-07-05 03:37:35 +04:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-13 08:56:25 +04:00
|
|
|
private applyError(): void {
|
|
|
|
|
this.title.set('');
|
|
|
|
|
this.safeHtml.set(this.sanitizer.bypassSecurityTrustHtml(''));
|
|
|
|
|
this.error.set(true);
|
|
|
|
|
this.loading.set(false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-05 03:37:35 +04:00
|
|
|
private applyPage(title: string, html: string, notFound: boolean): void {
|
|
|
|
|
if (notFound) {
|
|
|
|
|
this.title.set('');
|
|
|
|
|
this.safeHtml.set(this.sanitizer.bypassSecurityTrustHtml(''));
|
|
|
|
|
this.notFound.set(true);
|
|
|
|
|
this.loading.set(false);
|
2026-07-10 13:52:01 +04:00
|
|
|
this.titleService.setTitle('404');
|
2026-07-05 03:37:35 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sanitized = this.sanitizer.sanitize(SecurityContext.HTML, html) ?? '';
|
|
|
|
|
this.title.set(title);
|
|
|
|
|
this.safeHtml.set(this.sanitizer.bypassSecurityTrustHtml(sanitized));
|
|
|
|
|
this.notFound.set(false);
|
|
|
|
|
this.loading.set(false);
|
2026-07-10 13:52:01 +04:00
|
|
|
this.titleService.setTitle(title);
|
|
|
|
|
this.meta.updateTag({ name: 'description', content: title });
|
2026-07-05 03:37:35 +04:00
|
|
|
}
|
|
|
|
|
}
|