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>
This commit is contained in:
sdarbinyan
2026-07-18 00:12:19 +04:00
parent 6be3c5892d
commit eb7b5c996d
12 changed files with 1384 additions and 15 deletions

View File

@@ -3,6 +3,7 @@ import { languageGuard } from './guards/language.guard';
import { projectEditorDirtyGuard } from './features/project-editor/guards/project-editor-dirty.guard';
import { adminAuthGuard } from './core/admin-auth/admin-auth.guard';
import { adminCategoryDirtyGuard } from './features/admin/categories/guards/admin-category-dirty.guard';
import { AdminLayoutComponent } from './features/admin/shell/admin-layout.component';
import { environment } from '../environments/environment';
// Core routes (same across all brands)
@@ -50,41 +51,82 @@ const coreRoutes: Routes = [
{
path: 'backoffice',
canActivate: [adminAuthGuard],
component: AdminLayoutComponent,
children: [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{
path: 'dashboard',
loadComponent: () => import('./features/admin/dashboard/pages/admin-dashboard-page.component').then(m => m.AdminDashboardPageComponent)
loadComponent: () => import('./features/admin/dashboard/pages/admin-dashboard-page.component').then(m => m.AdminDashboardPageComponent),
data: {
titleKey: 'adminShell.pages.dashboard.title',
descriptionKey: 'adminShell.pages.dashboard.description',
breadcrumb: [{ labelKey: 'adminShell.pages.dashboard.title' }]
}
},
{
path: 'products',
loadComponent: () => import('./features/admin/products/pages/admin-products-list-page.component').then(m => m.AdminProductsListPageComponent)
loadComponent: () => import('./features/admin/products/pages/admin-products-list-page.component').then(m => m.AdminProductsListPageComponent),
data: {
titleKey: 'adminShell.pages.products.title',
descriptionKey: 'adminShell.pages.products.description',
breadcrumb: [{ labelKey: 'adminShell.nav.products' }]
}
},
{
path: 'products/create',
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent)
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
data: {
titleKey: 'adminShell.pages.productCreate.title',
descriptionKey: 'adminShell.pages.productCreate.description',
breadcrumb: [{ labelKey: 'adminShell.nav.products', path: ['products'] }, { labelKey: 'adminShell.pages.productCreate.title' }]
}
},
{
path: 'products/:id/edit',
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent)
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
data: {
titleKey: 'adminShell.pages.productEdit.title',
descriptionKey: 'adminShell.pages.productEdit.description',
breadcrumb: [{ labelKey: 'adminShell.nav.products', path: ['products'] }, { labelKey: 'adminShell.pages.productEdit.title' }]
}
},
{
path: 'products/:id/duplicate',
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent)
loadComponent: () => import('./features/admin/products/pages/admin-product-editor-page.component').then(m => m.AdminProductEditorPageComponent),
data: {
titleKey: 'adminShell.pages.productDuplicate.title',
descriptionKey: 'adminShell.pages.productDuplicate.description',
breadcrumb: [{ labelKey: 'adminShell.nav.products', path: ['products'] }, { labelKey: 'adminShell.pages.productDuplicate.title' }]
}
},
{
path: 'categories',
loadComponent: () => import('./features/admin/categories/pages/admin-categories-list-page.component').then(m => m.AdminCategoriesListPageComponent)
loadComponent: () => import('./features/admin/categories/pages/admin-categories-list-page.component').then(m => m.AdminCategoriesListPageComponent),
data: {
titleKey: 'adminShell.pages.categories.title',
descriptionKey: 'adminShell.pages.categories.description',
breadcrumb: [{ labelKey: 'adminShell.nav.categories' }]
}
},
{
path: 'categories/create',
loadComponent: () => import('./features/admin/categories/pages/admin-category-editor-page.component').then(m => m.AdminCategoryEditorPageComponent),
canDeactivate: [adminCategoryDirtyGuard]
canDeactivate: [adminCategoryDirtyGuard],
data: {
titleKey: 'adminShell.pages.categoryCreate.title',
descriptionKey: 'adminShell.pages.categoryCreate.description',
breadcrumb: [{ labelKey: 'adminShell.nav.categories', path: ['categories'] }, { labelKey: 'adminShell.pages.categoryCreate.title' }]
}
},
{
path: 'categories/:id/edit',
loadComponent: () => import('./features/admin/categories/pages/admin-category-editor-page.component').then(m => m.AdminCategoryEditorPageComponent),
canDeactivate: [adminCategoryDirtyGuard]
canDeactivate: [adminCategoryDirtyGuard],
data: {
titleKey: 'adminShell.pages.categoryEdit.title',
descriptionKey: 'adminShell.pages.categoryEdit.description',
breadcrumb: [{ labelKey: 'adminShell.nav.categories', path: ['categories'] }, { labelKey: 'adminShell.pages.categoryEdit.title' }]
}
},
{
// Static Pages is a first-class Project Editor module (Sprint X+2), not a
@@ -96,32 +138,66 @@ const coreRoutes: Routes = [
},
{
path: 'transactions',
loadComponent: () => import('./features/admin/transactions/pages/admin-transactions-list-page.component').then(m => m.AdminTransactionsListPageComponent)
loadComponent: () => import('./features/admin/transactions/pages/admin-transactions-list-page.component').then(m => m.AdminTransactionsListPageComponent),
data: {
titleKey: 'adminShell.pages.transactions.title',
descriptionKey: 'adminShell.pages.transactions.description',
breadcrumb: [{ labelKey: 'adminShell.nav.transactions' }]
}
},
{
path: 'orders',
loadComponent: () => import('./features/admin/orders/pages/admin-orders-list-page.component').then(m => m.AdminOrdersListPageComponent)
loadComponent: () => import('./features/admin/orders/pages/admin-orders-list-page.component').then(m => m.AdminOrdersListPageComponent),
data: {
titleKey: 'adminShell.pages.orders.title',
descriptionKey: 'adminShell.pages.orders.description',
breadcrumb: [{ labelKey: 'adminShell.nav.orders' }]
}
},
{
path: 'orders/:id',
loadComponent: () => import('./features/admin/orders/pages/admin-order-detail-page.component').then(m => m.AdminOrderDetailPageComponent)
loadComponent: () => import('./features/admin/orders/pages/admin-order-detail-page.component').then(m => m.AdminOrderDetailPageComponent),
data: {
titleKey: 'adminShell.pages.orderDetail.title',
descriptionKey: 'adminShell.pages.orderDetail.description',
breadcrumb: [{ labelKey: 'adminShell.nav.orders', path: ['orders'] }, { labelKey: 'adminShell.pages.orderDetail.title' }]
}
},
{
path: 'media',
loadComponent: () => import('./features/backoffice/media/media-library-page.component').then(m => m.MediaLibraryPageComponent),
data: { titleKey: 'dashboard.actionMediaLibrary' }
data: {
titleKey: 'adminShell.pages.media.title',
descriptionKey: 'adminShell.pages.media.description',
breadcrumb: [{ labelKey: 'adminShell.nav.mediaLibrary' }]
}
},
{
path: 'users',
loadComponent: () => import('./features/admin/users/pages/admin-users-page.component').then(m => m.AdminUsersPageComponent)
loadComponent: () => import('./features/admin/users/pages/admin-users-page.component').then(m => m.AdminUsersPageComponent),
data: {
titleKey: 'adminShell.pages.users.title',
descriptionKey: 'adminShell.pages.users.description',
breadcrumb: [{ labelKey: 'adminShell.nav.users' }]
}
},
{
path: 'monitoring',
loadComponent: () => import('./features/admin/monitoring/pages/admin-monitoring-page.component').then(m => m.AdminMonitoringPageComponent)
loadComponent: () => import('./features/admin/monitoring/pages/admin-monitoring-page.component').then(m => m.AdminMonitoringPageComponent),
data: {
titleKey: 'adminShell.pages.monitoring.title',
descriptionKey: 'adminShell.pages.monitoring.description',
breadcrumb: [{ labelKey: 'adminShell.nav.monitoring' }]
}
},
{
path: 'analytics',
loadComponent: () => import('./features/admin/analytics/pages/admin-analytics-page.component').then(m => m.AdminAnalyticsPageComponent)
loadComponent: () => import('./features/admin/analytics/pages/admin-analytics-page.component').then(m => m.AdminAnalyticsPageComponent),
data: {
titleKey: 'adminShell.pages.analytics.title',
descriptionKey: 'adminShell.pages.analytics.description',
breadcrumb: [{ labelKey: 'adminShell.nav.analytics' }]
}
},
{ path: '**', redirectTo: 'dashboard' }
]