diff --git a/apps/landing/src/app/page.tsx b/apps/landing/src/app/page.tsx index 26612e2..de8d2d4 100644 --- a/apps/landing/src/app/page.tsx +++ b/apps/landing/src/app/page.tsx @@ -1,3 +1,31 @@ +// Заглушка страницы: проверяем форму и CTA. Секции и вёрстку сделает дизайнер. +import { CtaButton } from '@/components/CtaButton'; +import { LeadForm } from '@/components/LeadForm'; +import { CTAS, FORM_TEXT } from '@/content/form'; +import { LEAD_SECTION_ID } from '@/lib/leadPrefill'; + +const ctaClass = 'rounded border border-neutral-900 px-4 py-2'; + export default function Page() { - return
Кварта
; + return ( +
+
+

Кварта — свежая обжарка в Минске

+

Обжариваем по четвергам, привозим по пятницам.

+
+ {Object.values(CTAS).map((cta) => ( + + {cta.label} + + ))} +
+
+ +
+

{FORM_TEXT.title}

+

{FORM_TEXT.lead}

+ +
+
+ ); } diff --git a/apps/landing/src/components/CtaButton.tsx b/apps/landing/src/components/CtaButton.tsx new file mode 100644 index 0000000..debea74 --- /dev/null +++ b/apps/landing/src/components/CtaButton.tsx @@ -0,0 +1,31 @@ +'use client'; +// Клиентский островок: кнопка CTA в серверной секции, предзаполняет форму заявки. +import type { Interest } from '@kvarta/shared/lead'; +import type { ReactNode } from 'react'; +import { prefillLead } from '@/lib/leadPrefill'; + +export type CtaButtonProps = { + interest: Interest; + /** комментарий для формы, например «Эфиопия Иргачеффе, 250 г» */ + comment?: string; + /** id секции-источника; по умолчанию — ближайший предок с id */ + source?: string; + className?: string; + children: ReactNode; +}; + +export function CtaButton({ interest, comment, source, className, children }: CtaButtonProps) { + return ( + + ); +} diff --git a/apps/landing/src/components/LeadForm.tsx b/apps/landing/src/components/LeadForm.tsx new file mode 100644 index 0000000..9db3f5c --- /dev/null +++ b/apps/landing/src/components/LeadForm.tsx @@ -0,0 +1,155 @@ +'use client'; +// Разметка формы заявки. Вся логика — в useLeadForm; вёрстку переделает дизайнер. +import { INTERESTS, INTEREST_LABELS, type LeadField } from '@kvarta/shared/lead'; +import { FORM_TEXT } from '@/content/form'; +import { LEAD_NAME_INPUT_ID } from '@/lib/leadPrefill'; +import type { SendLead } from '@/lib/sendLead'; +import { useLeadForm } from '@/lib/useLeadForm'; + +export type LeadFormProps = { + /** подмена отправки — для тестов и витрины состояний */ + sendLead?: SendLead; + className?: string; +}; + +const ids = { + name: LEAD_NAME_INPUT_ID, + phone: 'lead-phone', + interest: 'lead-interest', + comment: 'lead-comment', + company: 'lead-company', +} as const; + +const errorId = (field: LeadField) => `${ids[field]}-error`; + +function capitalize(s: string) { + return s.charAt(0).toUpperCase() + s.slice(1); +} + +export function LeadForm({ sendLead, className }: LeadFormProps) { + const form = useLeadForm({ sendLead }); + const { values, errors, status } = form; + + const fieldProps = (field: LeadField) => ({ + 'aria-invalid': errors[field] ? true : undefined, + 'aria-describedby': errors[field] ? errorId(field) : undefined, + 'data-invalid': errors[field] ? '' : undefined, + }); + + const fieldError = (field: LeadField) => + errors[field] ? ( +

+ {errors[field]} +

+ ) : null; + + return ( +
+
+
+ + form.setField('name', e.target.value)} + onBlur={() => form.blurField('name')} + {...fieldProps('name')} + /> + {fieldError('name')} +
+ +
+ + form.setField('phone', e.target.value)} + onBlur={() => form.blurField('phone')} + {...fieldProps('phone')} + /> + {fieldError('phone')} +
+ +
+ {FORM_TEXT.interest.legend} +
+ {INTERESTS.map((interest) => ( + + ))} +
+ {fieldError('interest')} +
+ +
+ +