feat(project-editor): footer validation rules (contact email, social link URLs, payment icons)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

Real gaps, not fabricated: isValidEmail existed in primitives.ts but was
never called anywhere; social-link URL check only lived as a per-row
template hint (never blocked publish or set the nav badge); payment icons
with only src or only alt set were silently accepted.

- invalid-contact-email: company.contacts.email must be a valid email (error)
- invalid-social-link-url: footer.socialLinks entries need a valid http(s) URL (warning)
- incomplete-payment-icon: a payment icon needs both src and alt, or neither (warning)

Wired into footer-section via the existing fieldError() pattern. Header has
no equivalent gap today (every header field is a bool/enum, always valid by
construction) so nothing was added there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-17 16:57:43 +04:00
parent 2a8c1166b1
commit 9e44215dd5
7 changed files with 51 additions and 1 deletions

View File

@@ -3,6 +3,7 @@ import { BootstrapConfig } from '../../../shared/models/config';
import { ProjectEditorSectionId } from '../models/project-editor.model';
import {
extractStyleBlocks,
isValidEmail,
isValidHexColor,
isValidHttpUrl,
normalizeRoute,
@@ -56,6 +57,9 @@ export class ProjectValidator {
...this.cssIssues(bootstrap),
...this.translationIssues(bootstrap),
...this.layoutIssues(bootstrap),
...this.footerContactIssues(bootstrap),
...this.footerSocialLinkIssues(bootstrap),
...this.footerPaymentIconIssues(bootstrap),
];
}
@@ -222,4 +226,26 @@ export class ProjectValidator {
? [error('invalid-layouts', 'builder.validationInvalidLayouts', 'theme', 'layout.type')]
: [];
}
private footerContactIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
const email = bootstrap.company?.contacts?.email;
return !email || isValidEmail(email)
? []
: [error('invalid-contact-email', 'builder.validationInvalidContactEmail', 'footer', 'company.contacts.email')];
}
private footerSocialLinkIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
const hasInvalidUrl = (bootstrap.footer?.socialLinks ?? []).some(link => !!link.url && !isValidHttpUrl(link.url));
return hasInvalidUrl
? [warning('invalid-social-link-url', 'builder.validationInvalidSocialLinkUrl', 'footer', 'footer.socialLinks')]
: [];
}
/** A payment icon row with only one of src/alt set is a broken image reference or missing accessibility text. */
private footerPaymentIconIssues(bootstrap: BootstrapConfig): ProjectValidationIssue[] {
const hasIncompleteRow = (bootstrap.footer?.paymentIcons ?? []).some(icon => !!icon.src !== !!icon.alt);
return hasIncompleteRow
? [warning('incomplete-payment-icon', 'builder.validationIncompletePaymentIcon', 'footer', 'footer.paymentIcons')]
: [];
}
}