Rename the two medcenter landings to their audience names: medcenter -> medcenterphysio, medcenterpersonal -> medcenterstart. Alongside the rename: - add a RevenueCalculator section to both landings; - rework the copy and figures in src/data/content.ts; - simplify the lead form: drop the "cabinet_state" and "profile" selects (along with SelectField and the matching fields in shared/lead.ts, lead-mapper.ts and amo-check.ts) and make company and email optional; - add the legacy/new static prototypes for both landings; - add pnpm-lock.yaml to medcenterstart (package-lock.json is still there too). deploy/apps.conf and deploy/README.md still refer to the old directory names and need a follow-up. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
import { utmKeys, type LeadFormId, type LeadInput, type LeadResponse } from '../../shared/lead'
|
|
|
|
const ENDPOINT = '/api/leads/medical-centers-existing-physio'
|
|
const DRAFT_KEY = 'exo_medcenter_lead_draft'
|
|
|
|
const FALLBACK_ERROR =
|
|
'Не удалось отправить форму автоматически. Данные сохранены в браузере — свяжитесь с нами по +7 939 717-80-80.'
|
|
|
|
/** Query-string UTM tags, forwarded to amoCRM with the lead. */
|
|
function collectUtm(): Record<string, string> {
|
|
const params = new URLSearchParams(window.location.search)
|
|
const utm: Record<string, string> = {}
|
|
for (const key of utmKeys) {
|
|
const value = params.get(key)
|
|
if (value) utm[key] = value
|
|
}
|
|
return utm
|
|
}
|
|
|
|
export interface SubmitResult {
|
|
ok: boolean
|
|
error?: string
|
|
fields?: Record<string, string>
|
|
}
|
|
|
|
export async function submitLead(
|
|
form: LeadFormId,
|
|
values: Omit<LeadInput, 'form' | 'utm' | 'page' | 'referrer'>,
|
|
): Promise<SubmitResult> {
|
|
const payload: LeadInput = {
|
|
...values,
|
|
form,
|
|
page: window.location.pathname,
|
|
referrer: document.referrer || undefined,
|
|
utm: collectUtm(),
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(ENDPOINT, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
const body = (await response.json().catch(() => null)) as LeadResponse | null
|
|
|
|
if (!response.ok || !body?.ok) {
|
|
// The page must never lose a lead just because the CRM is down.
|
|
saveDraft(payload)
|
|
return {
|
|
ok: false,
|
|
error: body && !body.ok ? body.error : FALLBACK_ERROR,
|
|
fields: body && !body.ok ? body.fields : undefined,
|
|
}
|
|
}
|
|
|
|
window.dataLayer = window.dataLayer ?? []
|
|
window.dataLayer.push({ event: 'medcenter_lead_sent', form })
|
|
localStorage.removeItem(DRAFT_KEY)
|
|
return { ok: true }
|
|
} catch (error) {
|
|
saveDraft(payload)
|
|
console.warn('Lead endpoint error', error, payload)
|
|
return { ok: false, error: FALLBACK_ERROR }
|
|
}
|
|
}
|
|
|
|
function saveDraft(payload: LeadInput) {
|
|
try {
|
|
localStorage.setItem(DRAFT_KEY, JSON.stringify(payload))
|
|
} catch {
|
|
// Private-mode browsers throw on write; the visible error message is enough.
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
dataLayer?: Record<string, unknown>[]
|
|
}
|
|
}
|