feat(landing): заглушка страницы с рабочей формой

This commit is contained in:
2026-10-17 15:47:24 +03:00
parent c2786f5894
commit 02da483747
9 changed files with 527 additions and 1 deletions
+66
View File
@@ -0,0 +1,66 @@
// Предзаполнение формы заявки из CTA в любой секции.
// Маленький store вместо контекста: секции остаются серверными компонентами,
// кнопки — клиентскими островками, форма подписывается на store сама.
import type { Interest } from '@kvarta/shared/lead';
export const LEAD_SECTION_ID = 'lead';
export const LEAD_NAME_INPUT_ID = 'lead-name';
export type LeadPrefillInput = {
interest: Interest;
comment?: string;
/** id секции, откуда пришла CTA, — пойдёт в submitPage последнего касания */
source?: string;
};
export type LeadPrefill = LeadPrefillInput & { seq: number };
type Listener = (prefill: LeadPrefill) => void;
let current: LeadPrefill | null = null;
const listeners = new Set<Listener>();
export function getLeadPrefill(): LeadPrefill | null {
return current;
}
export function subscribeLeadPrefill(listener: Listener): () => void {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
/** Выставить значения формы, плавно доскроллить до #lead и поставить фокус в поле имени. */
export function prefillLead(input: LeadPrefillInput): void {
current = { ...input, seq: (current?.seq ?? 0) + 1 };
for (const listener of listeners) listener(current);
if (typeof document === 'undefined') return;
const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
document
.getElementById(LEAD_SECTION_ID)
?.scrollIntoView({ behavior: reduceMotion ? 'auto' : 'smooth', block: 'start' });
// preventScroll — чтобы фокус не оборвал плавный скролл рывком к полю
document.getElementById(LEAD_NAME_INPUT_ID)?.focus({ preventScroll: true });
}
/**
* Применить предзаполнение к текущим значениям формы.
* Комментарий, который посетитель напечатал сам, не трогаем; свой прошлый предзаполненный — заменяем.
*/
export function applyPrefill<T extends { interest: Interest | ''; comment: string }>(
values: T,
prefill: LeadPrefillInput,
lastPrefilledComment: string | null,
): { values: T; prefilledComment: string | null } {
const typedByVisitor = values.comment.trim() !== '' && values.comment !== lastPrefilledComment;
if (typedByVisitor) {
return { values: { ...values, interest: prefill.interest }, prefilledComment: lastPrefilledComment };
}
const comment = prefill.comment ?? '';
return {
values: { ...values, interest: prefill.interest, comment },
prefilledComment: comment === '' ? null : comment,
};
}