feat(admin): sprint 19 admin dashboard, routing, i18n

- Add admin dashboard feature (models/gateway/facade/components/page)
- Wire admin/products routes and backoffice coming-soon placeholders
- Add lastPublishedAt to ProjectEditorFacade/state
- Add dashboard i18n keys (en/ru/hy) and docs/ADMIN.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-14 12:21:33 +04:00
parent 6aec2ebcb2
commit 325dc17911
36 changed files with 1386 additions and 5 deletions

View File

@@ -8,6 +8,8 @@ export interface ProjectValidationIssue {
const HEX_COLOR = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
const HTTP_URL = /^https?:\/\/\S+$/;
const KNOWN_PLATFORM_LAYOUT_TYPES = new Set(['default', 'sidebar-left', 'carousel-home', 'minimal']);
const KNOWN_SECTION_LAYOUT_STRATEGIES = new Set(['stack', 'grid', 'hero', 'carousel', 'split']);
@Injectable({ providedIn: 'root' })
export class ProjectValidator {
@@ -20,6 +22,8 @@ export class ProjectValidator {
...this.homepageIssues(bootstrap),
...this.navigationIssues(bootstrap),
...this.colorIssues(bootstrap),
...this.translationIssues(bootstrap),
...this.layoutIssues(bootstrap),
];
}
@@ -69,4 +73,35 @@ export class ProjectValidator {
const invalid = Object.values(bootstrap.theme.palette).some(value => !HEX_COLOR.test(value));
return invalid ? [{ code: 'invalid-colors', message: 'builder.validationInvalidColors' }] : [];
}
private translationIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
const otherLocales = bootstrap.localization.supportedLocales.filter(locale => locale !== bootstrap.localization.defaultLocale);
if (otherLocales.length === 0) {
return [];
}
const headerMissing = bootstrap.navigation.header.some(item => {
const label = item.label;
if (!label || typeof label !== 'object') {
return false;
}
return otherLocales.some(locale => !label[locale]?.trim());
});
const staticPages = bootstrap.staticPages;
const staticPagesMissing = staticPages && !Array.isArray(staticPages)
? Object.values(staticPages).some(page => page.translations && otherLocales.some(locale => !page.translations![locale]))
: false;
return headerMissing || staticPagesMissing ? [{ code: 'missing-translations', message: 'builder.validationMissingTranslations' }] : [];
}
private layoutIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
const invalidPlatformLayout = !!bootstrap.layout?.type && !KNOWN_PLATFORM_LAYOUT_TYPES.has(bootstrap.layout.type);
const invalidSectionLayout = bootstrap.pages.some(page =>
page.sections.some(section => !!section.layout?.strategy && !KNOWN_SECTION_LAYOUT_STRATEGIES.has(section.layout.strategy)),
);
return invalidPlatformLayout || invalidSectionLayout ? [{ code: 'invalid-layouts', message: 'builder.validationInvalidLayouts' }] : [];
}
}