feat(content-management): surface validation errors per-page
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Replace global duplicate-slug/empty-title banners with per-page indicators: a danger badge next to the offending page's heading, plus inline error text on the specific pageId/slug app-form-field. Made ContentPageService.normalizeSlug public (was private) so the component can match validation results to a given page's normalized slug without duplicating the normalization logic. Verified in browser: setting a duplicate slug live shows both the header badge and the inline field error immediately. Completes Sprint 3 (Static Page Generator): DRY cleanup, Design System adoption, SEO field coverage, per-page validation UX.
This commit is contained in:
@@ -4,13 +4,6 @@
|
||||
<app-button variant="primary" (click)="createPage()">{{ 'builder.createPage' | translate }}</app-button>
|
||||
</div>
|
||||
|
||||
@if (validation().duplicateSlugs.length > 0) {
|
||||
<app-badge variant="danger">{{ 'staticPages.duplicateSlug' | translate }}</app-badge>
|
||||
}
|
||||
@if (validation().emptyTitles.length > 0) {
|
||||
<app-badge variant="danger">{{ 'staticPages.emptyTitle' | translate }}</app-badge>
|
||||
}
|
||||
|
||||
@if (pages().length === 0) {
|
||||
<app-empty-state [title]="'builder.staticPages' | translate" [description]="'builder.createPage' | translate">
|
||||
<span slot="actions">
|
||||
@@ -24,7 +17,15 @@
|
||||
<app-card padding="md">
|
||||
<div class="page-card">
|
||||
<div class="page-card__header">
|
||||
<h3 class="page-card__title">{{ page.id }}</h3>
|
||||
<h3 class="page-card__title">
|
||||
{{ page.id }}
|
||||
@if (hasDuplicateSlug(page)) {
|
||||
<app-badge variant="danger">{{ 'staticPages.duplicateSlug' | translate }}</app-badge>
|
||||
}
|
||||
@if (hasEmptyTitle(page)) {
|
||||
<app-badge variant="danger">{{ 'staticPages.emptyTitle' | translate }}</app-badge>
|
||||
}
|
||||
</h3>
|
||||
<div class="editor-actions">
|
||||
<app-button variant="ghost" size="sm" (click)="move(page.id, -1)">↑</app-button>
|
||||
<app-button variant="ghost" size="sm" (click)="move(page.id, 1)">↓</app-button>
|
||||
@@ -33,10 +34,10 @@
|
||||
</div>
|
||||
|
||||
<div class="editor-grid three">
|
||||
<app-form-field [label]="'builder.pageId' | translate">
|
||||
<app-form-field [label]="'builder.pageId' | translate" [error]="hasEmptyTitle(page) ? ('staticPages.emptyTitle' | translate) : null">
|
||||
<app-input [ngModel]="page.id" (ngModelChange)="updatePage(page.id, { id: $event })" />
|
||||
</app-form-field>
|
||||
<app-form-field [label]="'builder.slug' | translate">
|
||||
<app-form-field [label]="'builder.slug' | translate" [error]="hasDuplicateSlug(page) ? ('staticPages.duplicateSlug' | translate) : null">
|
||||
<app-input [ngModel]="page.slug" (ngModelChange)="updatePage(page.id, { slug: $event })" />
|
||||
</app-form-field>
|
||||
<app-form-field [label]="'builder.iconLabel' | translate">
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
|
||||
.page-card__title {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #1e3c38);
|
||||
|
||||
@@ -81,6 +81,14 @@ export class StaticPagesEditorComponent {
|
||||
})));
|
||||
}
|
||||
|
||||
hasDuplicateSlug(page: ContentPage): boolean {
|
||||
return this.validation().duplicateSlugs.includes(this.contentFacade.normalizeSlug(page.slug));
|
||||
}
|
||||
|
||||
hasEmptyTitle(page: ContentPage): boolean {
|
||||
return this.validation().emptyTitles.includes(page.id);
|
||||
}
|
||||
|
||||
updateSeo(id: string, patch: Partial<ContentPageSeoConfig>): void {
|
||||
this.persist(this.pages().map(page => page.id !== id ? page : ({
|
||||
...page,
|
||||
|
||||
@@ -26,4 +26,8 @@ export class ContentManagementFacade {
|
||||
serializePages(pages: ContentPage[]) {
|
||||
return this.service.toBootstrapRecord(pages);
|
||||
}
|
||||
|
||||
normalizeSlug(value: string): string {
|
||||
return this.service.normalizeSlug(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ export class ContentPageService {
|
||||
return translations;
|
||||
}
|
||||
|
||||
private normalizeSlug(value: string): string {
|
||||
normalizeSlug(value: string): string {
|
||||
return value.replace(/^\/+/, '').trim();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user