import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { CdkDragDrop, DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop'; import { ProjectEditorFacade } from '../facade/project-editor.facade'; import { TranslatePipe } from '../../../i18n/translate.pipe'; import { TranslateService } from '../../../i18n/translate.service'; import { InputComponent } from '../../../shared/ui/input/input.component'; import { ButtonComponent } from '../../../shared/ui/button/button.component'; import { FormFieldComponent } from '../../../shared/ui/form-field/form-field.component'; import { SectionCardComponent } from '../../../shared/ui/section-card/section-card.component'; import { KeyValueEditorComponent } from '../../../shared/ui/key-value-editor/key-value-editor.component'; import { ImageFieldComponent } from '../../../shared/ui/image-field/image-field.component'; import { FooterColumnConfig, FooterLinkConfig, FooterPaymentIconConfig, FooterSocialLinkConfig } from '../../../shared/models/config'; import { isValidHttpUrl } from '../schema/validators/primitives'; import { ContentPageService } from '../../content-management/services/content-page.service'; @Component({ selector: 'app-project-editor-footer-section', standalone: true, imports: [ FormsModule, DragDropModule, TranslatePipe, InputComponent, ButtonComponent, FormFieldComponent, SectionCardComponent, KeyValueEditorComponent, ImageFieldComponent ], templateUrl: './footer-section.component.html', styleUrls: ['./section.shared.scss', './footer-section.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class ProjectEditorFooterSectionComponent { private readonly facade = inject(ProjectEditorFacade); private readonly translate = inject(TranslateService); private readonly contentPageService = inject(ContentPageService); readonly bootstrap = this.facade.bootstrap; readonly fieldError = (key: string): string | null => { const messageKey = this.facade.fieldError(key); return messageKey ? this.translate.t(messageKey) : null; }; /** Existing static pages, offered as the primary way to link a footer column - stays correct if a page's route changes later. */ readonly availablePages = computed(() => this.contentPageService.normalizePages(this.bootstrap()?.staticPages)); readonly copyrightValue = computed(() => { const value = this.bootstrap()?.footer?.copyrightText; return typeof value === 'string' ? value : ''; }); readonly paymentIconRows = computed(() => this.bootstrap()?.footer?.paymentIcons ?? []); readonly socialLinkRows = computed(() => this.bootstrap()?.footer?.socialLinks ?? []); readonly columns = computed(() => this.bootstrap()?.footer?.columns ?? []); readonly additionalPhones = computed(() => this.bootstrap()?.company.contacts.additionalPhones ?? []); readonly additionalEmails = computed(() => this.bootstrap()?.company.contacts.additionalEmails ?? []); readonly createPaymentIconRow = (): FooterPaymentIconConfig => ({ src: '', alt: '' }); readonly createSocialLinkRow = (): FooterSocialLinkConfig => ({ id: `social-${Date.now()}`, label: '', url: '' }); updateCompanyName(value: string): void { this.facade.updateBootstrap(current => ({ ...current, company: { ...current.company, companyName: value } })); } updateAddress(value: string): void { this.facade.updateBootstrap(current => ({ ...current, company: { ...current.company, address: { ...current.company.address, street: value } } })); } updatePhone(value: string): void { this.facade.updateBootstrap(current => ({ ...current, company: { ...current.company, contacts: { ...current.company.contacts, phone: value } }, branding: { ...current.branding, supportPhone: value } })); } updateEmail(value: string): void { this.facade.updateBootstrap(current => ({ ...current, company: { ...current.company, contacts: { ...current.company.contacts, email: value } }, branding: { ...current.branding, supportEmail: value } })); } updateCopyright(value: string): void { this.facade.updateBootstrap(current => ({ ...current, footer: { ...current.footer, copyrightText: value } })); } updateLogo(value: string): void { this.facade.updateBootstrap(current => ({ ...current, footer: { ...current.footer, logoUrl: value } })); } onPaymentIconsChange(rows: FooterPaymentIconConfig[]): void { this.facade.updateBootstrap(current => ({ ...current, footer: { ...current.footer, paymentIcons: rows } })); } updatePaymentIconSrc(index: number, src: string): void { const rows = this.paymentIconRows().map((row, i) => (i === index ? { ...row, src } : row)); this.onPaymentIconsChange(rows); } updatePaymentIconAlt(index: number, alt: string): void { const rows = this.paymentIconRows().map((row, i) => (i === index ? { ...row, alt } : row)); this.onPaymentIconsChange(rows); } onSocialLinksChange(rows: FooterSocialLinkConfig[]): void { this.facade.updateBootstrap(current => ({ ...current, footer: { ...current.footer, socialLinks: rows } })); } updateSocialLinkLabel(index: number, label: string): void { const rows = this.socialLinkRows().map((row, i) => (i === index ? { ...row, label } : row)); this.onSocialLinksChange(rows); } updateSocialLinkUrl(index: number, url: string): void { const rows = this.socialLinkRows().map((row, i) => (i === index ? { ...row, url } : row)); this.onSocialLinksChange(rows); } isValidUrl(url: string): boolean { return !url || isValidHttpUrl(url); } // --- Footer columns (replaces the old comma-separated static-page-keys input) --- private setColumns(columns: FooterColumnConfig[]): void { this.facade.updateBootstrap(current => ({ ...current, footer: { ...current.footer, columns } })); } addColumn(): void { this.setColumns([...this.columns(), { id: `col-${Date.now()}`, title: '', links: [] }]); } removeColumn(columnId: string): void { this.setColumns(this.columns().filter(column => column.id !== columnId)); } updateColumnTitle(columnId: string, title: string): void { this.setColumns(this.columns().map(column => column.id === columnId ? { ...column, title } : column)); } dropColumn(event: CdkDragDrop): void { const columns = [...this.columns()]; moveItemInArray(columns, event.previousIndex, event.currentIndex); this.setColumns(columns); } addLink(columnId: string): void { const link: FooterLinkConfig = { id: `link-${Date.now()}`, label: '', pageKey: this.availablePages()[0]?.id ?? '', url: '' }; this.setColumns(this.columns().map(column => column.id === columnId ? { ...column, links: [...column.links, link] } : column)); } removeLink(columnId: string, linkId: string): void { this.setColumns(this.columns().map(column => column.id === columnId ? { ...column, links: column.links.filter(link => link.id !== linkId) } : column)); } updateLink(columnId: string, linkId: string, patch: Partial): void { this.setColumns(this.columns().map(column => column.id === columnId ? { ...column, links: column.links.map(link => link.id === linkId ? { ...link, ...patch } : link) } : column)); } dropLink(columnId: string, event: CdkDragDrop): void { const column = this.columns().find(c => c.id === columnId); if (!column) { return; } const links = [...column.links]; moveItemInArray(links, event.previousIndex, event.currentIndex); this.setColumns(this.columns().map(c => c.id === columnId ? { ...c, links } : c)); } linkSourceMode(link: FooterLinkConfig): 'page' | 'custom' { return link.pageKey ? 'page' : 'custom'; } setLinkSourceMode(columnId: string, linkId: string, mode: 'page' | 'custom'): void { if (mode === 'page') { this.updateLink(columnId, linkId, { pageKey: this.availablePages()[0]?.id ?? '', url: '' }); } else { this.updateLink(columnId, linkId, { pageKey: '' }); } } // --- Multiple phones/emails --- private setAdditionalPhones(phones: string[]): void { this.facade.updateBootstrap(current => ({ ...current, company: { ...current.company, contacts: { ...current.company.contacts, additionalPhones: phones } } })); } private setAdditionalEmails(emails: string[]): void { this.facade.updateBootstrap(current => ({ ...current, company: { ...current.company, contacts: { ...current.company.contacts, additionalEmails: emails } } })); } addPhone(): void { this.setAdditionalPhones([...this.additionalPhones(), '']); } updatePhoneAt(index: number, value: string): void { this.setAdditionalPhones(this.additionalPhones().map((phone, i) => i === index ? value : phone)); } removePhoneAt(index: number): void { this.setAdditionalPhones(this.additionalPhones().filter((_, i) => i !== index)); } addEmail(): void { this.setAdditionalEmails([...this.additionalEmails(), '']); } updateEmailAt(index: number, value: string): void { this.setAdditionalEmails(this.additionalEmails().map((email, i) => i === index ? value : email)); } removeEmailAt(index: number): void { this.setAdditionalEmails(this.additionalEmails().filter((_, i) => i !== index)); } }