2026-01-18 18:57:06 +04:00
|
|
|
|
2026-02-19 01:23:25 +04:00
|
|
|
import { Component, OnInit, signal, ApplicationRef, inject, DestroyRef } from '@angular/core';
|
2026-02-14 01:28:08 +04:00
|
|
|
import { Router, RouterOutlet, NavigationEnd } from '@angular/router';
|
2026-01-18 18:57:06 +04:00
|
|
|
import { Title } from '@angular/platform-browser';
|
|
|
|
|
import { HeaderComponent } from './components/header/header.component';
|
|
|
|
|
import { FooterComponent } from './components/footer/footer.component';
|
2026-02-14 01:28:08 +04:00
|
|
|
import { BackButtonComponent } from './components/back-button/back-button.component';
|
2026-02-19 01:23:25 +04:00
|
|
|
import { interval, concat } from 'rxjs';
|
2026-02-14 01:28:08 +04:00
|
|
|
import { filter, first } from 'rxjs/operators';
|
2026-02-19 01:23:25 +04:00
|
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
2026-01-18 18:57:06 +04:00
|
|
|
import { SwUpdate } from '@angular/service-worker';
|
2026-02-26 23:09:20 +04:00
|
|
|
import { TranslatePipe } from './i18n/translate.pipe';
|
|
|
|
|
import { TranslateService } from './i18n/translate.service';
|
2026-07-03 02:05:25 +04:00
|
|
|
import { PlatformRuntimeService } from './core/runtime/platform-runtime.service';
|
2026-07-05 00:38:21 +04:00
|
|
|
import { UiRuntimeFacade } from './facades/runtime/ui-runtime.facade';
|
2026-07-05 02:24:16 +04:00
|
|
|
import { ApiHealthService } from './services/api-health.service';
|
2026-07-09 01:13:54 +04:00
|
|
|
import { FloatingNotificationsComponent } from './features/website/user-experience/components/floating-notifications/floating-notifications.component';
|
2026-07-14 09:50:03 +04:00
|
|
|
import { AdminAuthService } from './core/admin-auth/admin-auth.service';
|
|
|
|
|
import { AuthService } from './services/auth.service';
|
2026-07-14 10:13:59 +04:00
|
|
|
import { TelegramLoginComponent } from './components/telegram-login/telegram-login.component';
|
2026-01-18 18:57:06 +04:00
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'app-root',
|
2026-07-14 10:13:59 +04:00
|
|
|
imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent, TranslatePipe, FloatingNotificationsComponent, TelegramLoginComponent],
|
2026-01-18 18:57:06 +04:00
|
|
|
templateUrl: './app.html',
|
|
|
|
|
styleUrl: './app.scss'
|
|
|
|
|
})
|
2026-02-19 01:23:25 +04:00
|
|
|
export class App implements OnInit {
|
2026-07-05 00:38:21 +04:00
|
|
|
protected title = '';
|
2026-02-14 01:28:08 +04:00
|
|
|
isHomePage = signal(true);
|
feat(admin): build shared admin shell
Sidebar (Dashboard/Catalog group/Products/Categories/Orders/Transactions/
Reviews/Reports/Content/Media/Marketplace Builder/Users/Settings/
Monitoring/Analytics + Documentation/Help/Logout), sticky topbar
(breadcrumbs, page title/description, search, notifications, tenant
selector and quick-publish placeholders, current user), reserved
right-rail slot, scrollable content area. Desktop 280px sidebar, tablet
icon rail, mobile drawer with focus management and Escape-to-close.
Nav items without a built page (Reviews, Reports, Settings, Docs, Help)
render disabled with a coming-soon badge instead of dead links; Content
and Marketplace Builder route to the existing project-editor pages
(static-pages / general) rather than duplicating them.
All 15 /backoffice/** routes now render through AdminLayoutComponent;
the public storefront header/back-button/footer no longer render on
admin routes (app.ts/app.html gate on a new isAdminRoute signal).
Added the adminShell i18n namespace (ru/en/hy) for every new shell
string so this doesn't add to the existing untranslated-admin-UI gap
tracked in KNOWN-ISSUES.md.
Colors/type sizes follow DESIGN.md tokens; the two rgba() modal-scrim
values are a documented, intentional exception (neutral overlay,
not a themed token).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 00:12:19 +04:00
|
|
|
isAdminRoute = signal(false);
|
2026-02-19 01:23:25 +04:00
|
|
|
checkingServer = signal(true);
|
|
|
|
|
serverAvailable = signal(false);
|
2026-01-18 18:57:06 +04:00
|
|
|
|
2026-02-19 01:23:25 +04:00
|
|
|
private destroyRef = inject(DestroyRef);
|
|
|
|
|
private titleService = inject(Title);
|
|
|
|
|
private swUpdate = inject(SwUpdate);
|
|
|
|
|
private appRef = inject(ApplicationRef);
|
|
|
|
|
private router = inject(Router);
|
2026-02-26 23:09:20 +04:00
|
|
|
private i18n = inject(TranslateService);
|
2026-07-03 02:05:25 +04:00
|
|
|
private platformRuntime = inject(PlatformRuntimeService);
|
2026-07-05 00:38:21 +04:00
|
|
|
private uiRuntime = inject(UiRuntimeFacade);
|
2026-07-05 02:24:16 +04:00
|
|
|
private apiHealth = inject(ApiHealthService);
|
2026-07-14 09:50:03 +04:00
|
|
|
private authService = inject(AuthService);
|
|
|
|
|
private adminAuthService = inject(AdminAuthService);
|
2026-01-18 18:57:06 +04:00
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
2026-07-03 02:05:25 +04:00
|
|
|
this.platformRuntime.initialize();
|
2026-07-05 00:38:21 +04:00
|
|
|
this.title = this.uiRuntime.marketplaceName();
|
|
|
|
|
this.titleService.setTitle(`${this.uiRuntime.marketplaceDisplayName()} - ${this.i18n.t('app.pageTitle')}`);
|
2026-01-18 18:57:06 +04:00
|
|
|
this.checkServerHealth();
|
|
|
|
|
this.setupAutoUpdates();
|
2026-07-14 09:50:03 +04:00
|
|
|
this.openLoginDialogsFromTestModeQueryParams();
|
2026-02-14 01:28:08 +04:00
|
|
|
|
|
|
|
|
// Track route changes to show/hide back button
|
2026-02-19 01:23:25 +04:00
|
|
|
this.router.events
|
|
|
|
|
.pipe(
|
|
|
|
|
filter(event => event instanceof NavigationEnd),
|
|
|
|
|
takeUntilDestroyed(this.destroyRef)
|
|
|
|
|
)
|
2026-02-14 01:28:08 +04:00
|
|
|
.subscribe((event) => {
|
2026-02-26 21:54:21 +04:00
|
|
|
const navEnd = event as NavigationEnd;
|
|
|
|
|
const url = navEnd.urlAfterRedirects || navEnd.url;
|
2026-02-26 22:23:08 +04:00
|
|
|
// Home pages: /ru, /en, /hy (with or without trailing slash)
|
|
|
|
|
this.isHomePage.set(/^\/[a-z]{2}\/?$/.test(url) || url === '/' || url === '');
|
feat(admin): build shared admin shell
Sidebar (Dashboard/Catalog group/Products/Categories/Orders/Transactions/
Reviews/Reports/Content/Media/Marketplace Builder/Users/Settings/
Monitoring/Analytics + Documentation/Help/Logout), sticky topbar
(breadcrumbs, page title/description, search, notifications, tenant
selector and quick-publish placeholders, current user), reserved
right-rail slot, scrollable content area. Desktop 280px sidebar, tablet
icon rail, mobile drawer with focus management and Escape-to-close.
Nav items without a built page (Reviews, Reports, Settings, Docs, Help)
render disabled with a coming-soon badge instead of dead links; Content
and Marketplace Builder route to the existing project-editor pages
(static-pages / general) rather than duplicating them.
All 15 /backoffice/** routes now render through AdminLayoutComponent;
the public storefront header/back-button/footer no longer render on
admin routes (app.ts/app.html gate on a new isAdminRoute signal).
Added the adminShell i18n namespace (ru/en/hy) for every new shell
string so this doesn't add to the existing untranslated-admin-UI gap
tracked in KNOWN-ISSUES.md.
Colors/type sizes follow DESIGN.md tokens; the two rgba() modal-scrim
values are a documented, intentional exception (neutral overlay,
not a themed token).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 00:12:19 +04:00
|
|
|
// Admin backoffice owns its own shell (AdminLayoutComponent) - the
|
|
|
|
|
// storefront header/back-button/footer never render there.
|
|
|
|
|
this.isAdminRoute.set(/^\/[a-z]{2}\/backoffice(\/|$|\?)/.test(url));
|
2026-02-14 01:28:08 +04:00
|
|
|
});
|
2026-01-18 18:57:06 +04:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 01:23:25 +04:00
|
|
|
private checkServerHealth(): void {
|
|
|
|
|
this.checkingServer.set(true);
|
2026-07-05 02:24:16 +04:00
|
|
|
this.apiHealth.ping()
|
2026-02-19 01:23:25 +04:00
|
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
|
|
|
.subscribe({
|
|
|
|
|
next: () => {
|
|
|
|
|
this.serverAvailable.set(true);
|
|
|
|
|
this.checkingServer.set(false);
|
|
|
|
|
},
|
|
|
|
|
error: () => {
|
|
|
|
|
this.serverAvailable.set(false);
|
|
|
|
|
this.checkingServer.set(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retryConnection(): void {
|
|
|
|
|
this.checkServerHealth();
|
2026-01-18 18:57:06 +04:00
|
|
|
}
|
|
|
|
|
|
2026-07-15 00:58:21 +04:00
|
|
|
/**
|
|
|
|
|
* ?login=true / ?adminLogin=true open the respective login dialog for
|
|
|
|
|
* manual testing. ?devBypassAdmin=true skips the QR flow entirely and
|
|
|
|
|
* activates a fake local admin session - dev builds only, no effect (and
|
|
|
|
|
* no-ops server-side too, see AdminAuthService.devBypassLogin) in
|
|
|
|
|
* production. No effect when the params are absent.
|
|
|
|
|
*/
|
2026-07-14 09:50:03 +04:00
|
|
|
private openLoginDialogsFromTestModeQueryParams(): void {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
if (params.get('login') === 'true') {
|
|
|
|
|
this.authService.requestLogin();
|
|
|
|
|
}
|
|
|
|
|
if (params.get('adminLogin') === 'true') {
|
|
|
|
|
this.adminAuthService.requestLogin();
|
|
|
|
|
}
|
2026-07-15 00:58:21 +04:00
|
|
|
if (params.get('devBypassAdmin') === 'true') {
|
|
|
|
|
this.adminAuthService.devBypassLogin();
|
|
|
|
|
}
|
2026-07-14 09:50:03 +04:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 01:23:25 +04:00
|
|
|
private setupAutoUpdates(): void {
|
2026-01-18 18:57:06 +04:00
|
|
|
if (!this.swUpdate.isEnabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const appIsStable$ = this.appRef.isStable.pipe(first(isStable => isStable === true));
|
|
|
|
|
const every6Hours$ = interval(6 * 60 * 60 * 1000);
|
|
|
|
|
const checkInterval$ = concat(appIsStable$, every6Hours$);
|
|
|
|
|
|
2026-02-19 01:23:25 +04:00
|
|
|
checkInterval$
|
|
|
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
|
|
|
.subscribe(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await this.swUpdate.checkForUpdate();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('Update check failed:', err);
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-01-18 18:57:06 +04:00
|
|
|
}
|
|
|
|
|
}
|