content: replace scaffolding with bilingual pages and case studies
Ship the final German and English copy, compose every route from shared page primitives, and cover claims, parity and contact briefing in tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
73
src/app/shared/contact-briefing/contact-briefing.ts
Normal file
73
src/app/shared/contact-briefing/contact-briefing.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
|
||||
import {
|
||||
type ContactFieldCopy,
|
||||
type ContactFieldId,
|
||||
type ContactPageCopy,
|
||||
} from '../../core/content/content.contracts';
|
||||
import { SITE_CONFIG } from '../../core/content/site-config';
|
||||
|
||||
const EMPTY_VALUES: Record<ContactFieldId, string> = {
|
||||
name: '',
|
||||
email: '',
|
||||
company: '',
|
||||
projectType: '',
|
||||
situation: '',
|
||||
timeframe: '',
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'app-contact-briefing',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './contact-briefing.html',
|
||||
styleUrl: './contact-briefing.scss',
|
||||
})
|
||||
export class ContactBriefing {
|
||||
readonly copy = input.required<ContactPageCopy>();
|
||||
|
||||
protected readonly values = signal<Record<ContactFieldId, string>>({ ...EMPTY_VALUES });
|
||||
protected readonly contactEmail = SITE_CONFIG.contactEmail;
|
||||
protected readonly calendarUrl = SITE_CONFIG.calendarUrl;
|
||||
|
||||
protected readonly missingLabels = computed(() => {
|
||||
const copy = this.copy();
|
||||
const values = this.values();
|
||||
return copy.fields
|
||||
.filter((field) => field.required && values[field.id].trim().length === 0)
|
||||
.map((field) => field.label);
|
||||
});
|
||||
|
||||
protected readonly incompleteMessage = computed(() => {
|
||||
const missing = this.missingLabels();
|
||||
if (missing.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `${this.copy().incompleteHint} ${missing.join(', ')}`;
|
||||
});
|
||||
|
||||
protected readonly mailtoHref = computed(() => {
|
||||
const copy = this.copy();
|
||||
const values = this.values();
|
||||
const body = this.assembleBody(copy, values);
|
||||
return `mailto:${SITE_CONFIG.contactEmail}?subject=${encodeURIComponent(copy.mailSubject)}&body=${encodeURIComponent(body)}`;
|
||||
});
|
||||
|
||||
protected readonly showCalendar = computed(() => {
|
||||
const url = SITE_CONFIG.calendarUrl;
|
||||
return typeof url === 'string' && url.length > 0;
|
||||
});
|
||||
|
||||
protected onInput(fieldId: ContactFieldId, event: Event): void {
|
||||
const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
||||
this.values.update((current) => ({ ...current, [fieldId]: target.value }));
|
||||
}
|
||||
|
||||
protected controlId(field: ContactFieldCopy): string {
|
||||
return `contact-${field.id}`;
|
||||
}
|
||||
|
||||
private assembleBody(copy: ContactPageCopy, values: Record<ContactFieldId, string>): string {
|
||||
const lines = copy.fields.map((field) => `${field.label}: ${values[field.id]}`);
|
||||
return [copy.mailIntro, '', ...lines, '', copy.mailSignature].join('\n');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user