analytics: count a successful form as the FORM_SUCCESS Metrika goal
The counter was in place but nothing reached it: src/lib/lead.ts only pushed
a custom dataLayer event, which Metrika's ecommerce: 'dataLayer' ignores, so
form conversions were not counted at all.
All five forms — including the fitnes callback modal — funnel through the one
success branch in submitLead(), so a single ym(112352796, 'reachGoal',
'FORM_SUCCESS') per landing covers every one of them. The counter id is
hardcoded to match the inline snippet in index.html; two sources for it would
drift. The call is optional-chained and wrapped in try/catch, because losing
analytics must never cost a lead.
The honeypot is the one success that is not a conversion: the server answers
bots with 200 {ok:true, leadId:0} so they learn nothing, and leadId === 0 is
what keeps that answer out of the goal.
The goal itself still has to be created in the counter's settings as a
JavaScript event named FORM_SUCCESS — noted in deploy/README.md, replacing
the entry that said conversions were unwired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e13854e918
commit
8af2e463b3
+6
-4
@@ -243,8 +243,10 @@ Also enable reg.ru VPS snapshots — a full-image restore beats rebuilding under
|
||||
- **One Metrika counter for four domains.** All four landings carry the same
|
||||
Yandex.Metrika counter (`112352796`) in `index.html`, so reports mix the domains and
|
||||
every domain has to be listed in the counter's settings, or its hits get filtered.
|
||||
- **Still open before launch** (not deployment blockers): conversions are not wired to
|
||||
Metrika goals — `src/lib/lead.ts` pushes a plain custom `dataLayer` event, which
|
||||
Metrika's `ecommerce: 'dataLayer'` does not read, so a JS goal or an explicit
|
||||
`ym(..., 'reachGoal', ...)` call is still needed; and the forms show implicit 152-ФЗ
|
||||
- **The `FORM_SUCCESS` goal has to exist in the Metrika UI.** `src/lib/lead.ts` fires
|
||||
`ym(112352796, 'reachGoal', 'FORM_SUCCESS')` on every successful form submission, but
|
||||
the goal itself is not part of the code: create it in the counter's settings as
|
||||
«JavaScript-событие» with the identifier `FORM_SUCCESS`, or the hits arrive and no
|
||||
report ever shows them. The honeypot answer (`leadId: 0`) deliberately does not count.
|
||||
- **Still open before launch** (not a deployment blocker): the forms show implicit 152-ФЗ
|
||||
consent text with no link to a published privacy policy.
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
const ENDPOINT = '/api/leads/fitness-centers'
|
||||
const DRAFT_KEY = 'exo_fitness_lead_draft'
|
||||
|
||||
/** Тот же счётчик, что и в инлайн-снипете index.html — один на все четыре лендинга. */
|
||||
const METRIKA_ID = 112352796
|
||||
|
||||
/** Query-string UTM tags, forwarded to amoCRM with the lead. */
|
||||
function collectUtm(): Record<string, string> {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
@@ -59,6 +62,8 @@ export async function submitLead(form: LeadFormId, values: Omit<LeadInput, 'form
|
||||
|
||||
window.dataLayer = window.dataLayer ?? []
|
||||
window.dataLayer.push({ event: 'fitness_lead_sent', form, configuration: values.configuration })
|
||||
// leadId === 0 — ответ-обманка ханипота: сделки в amoCRM нет, конверсии тоже.
|
||||
if (body.leadId !== 0) reachGoal('FORM_SUCCESS')
|
||||
localStorage.removeItem(DRAFT_KEY)
|
||||
return { ok: true }
|
||||
} catch (error) {
|
||||
@@ -80,6 +85,20 @@ function saveDraft(payload: LeadInput) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цель Метрики. `ym` объявляется синхронно инлайн-снипетом и до загрузки tag.js
|
||||
* копит вызовы в очереди, так что ждать загрузки счётчика не нужно; опциональный
|
||||
* вызов — страховка на случай блокировщика, вырезавшего снипет целиком.
|
||||
*/
|
||||
function reachGoal(goal: string) {
|
||||
try {
|
||||
window.ym?.(METRIKA_ID, 'reachGoal', goal)
|
||||
} catch (error) {
|
||||
// Аналитика не имеет права ломать отправку формы.
|
||||
console.warn('Metrika reachGoal failed', error)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Клик по кнопке звонка */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -153,5 +172,6 @@ export function reportCallClick(): void {
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer?: Record<string, unknown>[]
|
||||
ym?: (counterId: number, action: string, ...args: unknown[]) => void
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
const ENDPOINT = '/api/leads/hotels'
|
||||
const DRAFT_KEY = 'exo_hotel_lead_draft'
|
||||
|
||||
/** Тот же счётчик, что и в инлайн-снипете index.html — один на все четыре лендинга. */
|
||||
const METRIKA_ID = 112352796
|
||||
|
||||
/** Query-string UTM tags, forwarded to amoCRM with the lead. */
|
||||
function collectUtm(): Record<string, string> {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
@@ -62,6 +65,8 @@ export async function submitLead(
|
||||
|
||||
window.dataLayer = window.dataLayer ?? []
|
||||
window.dataLayer.push({ event: 'hotel_lead_sent', form })
|
||||
// leadId === 0 — ответ-обманка ханипота: сделки в amoCRM нет, конверсии тоже.
|
||||
if (body.leadId !== 0) reachGoal('FORM_SUCCESS')
|
||||
localStorage.removeItem(DRAFT_KEY)
|
||||
return { ok: true }
|
||||
} catch (error) {
|
||||
@@ -83,6 +88,20 @@ function saveDraft(payload: LeadInput) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цель Метрики. `ym` объявляется синхронно инлайн-снипетом и до загрузки tag.js
|
||||
* копит вызовы в очереди, так что ждать загрузки счётчика не нужно; опциональный
|
||||
* вызов — страховка на случай блокировщика, вырезавшего снипет целиком.
|
||||
*/
|
||||
function reachGoal(goal: string) {
|
||||
try {
|
||||
window.ym?.(METRIKA_ID, 'reachGoal', goal)
|
||||
} catch (error) {
|
||||
// Аналитика не имеет права ломать отправку формы.
|
||||
console.warn('Metrika reachGoal failed', error)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Клик по кнопке звонка */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -156,5 +175,6 @@ export function reportCallClick(): void {
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer?: Record<string, unknown>[]
|
||||
ym?: (counterId: number, action: string, ...args: unknown[]) => void
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
const ENDPOINT = '/api/leads/medical-centers-existing-physio'
|
||||
const DRAFT_KEY = 'exo_medcenter_lead_draft'
|
||||
|
||||
/** Тот же счётчик, что и в инлайн-снипете index.html — один на все четыре лендинга. */
|
||||
const METRIKA_ID = 112352796
|
||||
|
||||
const FALLBACK_ERROR =
|
||||
'Не удалось отправить форму автоматически. Данные сохранены в браузере — свяжитесь с нами по +7 927 789-60-71.'
|
||||
|
||||
@@ -62,6 +65,8 @@ export async function submitLead(
|
||||
|
||||
window.dataLayer = window.dataLayer ?? []
|
||||
window.dataLayer.push({ event: 'medcenter_lead_sent', form })
|
||||
// leadId === 0 — ответ-обманка ханипота: сделки в amoCRM нет, конверсии тоже.
|
||||
if (body.leadId !== 0) reachGoal('FORM_SUCCESS')
|
||||
localStorage.removeItem(DRAFT_KEY)
|
||||
return { ok: true }
|
||||
} catch (error) {
|
||||
@@ -79,6 +84,20 @@ function saveDraft(payload: LeadInput) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цель Метрики. `ym` объявляется синхронно инлайн-снипетом и до загрузки tag.js
|
||||
* копит вызовы в очереди, так что ждать загрузки счётчика не нужно; опциональный
|
||||
* вызов — страховка на случай блокировщика, вырезавшего снипет целиком.
|
||||
*/
|
||||
function reachGoal(goal: string) {
|
||||
try {
|
||||
window.ym?.(METRIKA_ID, 'reachGoal', goal)
|
||||
} catch (error) {
|
||||
// Аналитика не имеет права ломать отправку формы.
|
||||
console.warn('Metrika reachGoal failed', error)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Клик по кнопке звонка */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -152,5 +171,6 @@ export function reportCallClick(): void {
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer?: Record<string, unknown>[]
|
||||
ym?: (counterId: number, action: string, ...args: unknown[]) => void
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
const ENDPOINT = '/api/leads/medical-centers-no-physio'
|
||||
const DRAFT_KEY = 'exo_medcenter_no_physio_lead_draft'
|
||||
|
||||
/** Тот же счётчик, что и в инлайн-снипете index.html — один на все четыре лендинга. */
|
||||
const METRIKA_ID = 112352796
|
||||
|
||||
const FALLBACK_ERROR =
|
||||
'Не удалось отправить форму автоматически. Данные сохранены в браузере — свяжитесь с нами по +7 927 789-60-71.'
|
||||
|
||||
@@ -62,6 +65,8 @@ export async function submitLead(
|
||||
|
||||
window.dataLayer = window.dataLayer ?? []
|
||||
window.dataLayer.push({ event: 'medcenter_no_physio_lead_sent', form })
|
||||
// leadId === 0 — ответ-обманка ханипота: сделки в amoCRM нет, конверсии тоже.
|
||||
if (body.leadId !== 0) reachGoal('FORM_SUCCESS')
|
||||
localStorage.removeItem(DRAFT_KEY)
|
||||
return { ok: true }
|
||||
} catch (error) {
|
||||
@@ -79,6 +84,20 @@ function saveDraft(payload: LeadInput) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Цель Метрики. `ym` объявляется синхронно инлайн-снипетом и до загрузки tag.js
|
||||
* копит вызовы в очереди, так что ждать загрузки счётчика не нужно; опциональный
|
||||
* вызов — страховка на случай блокировщика, вырезавшего снипет целиком.
|
||||
*/
|
||||
function reachGoal(goal: string) {
|
||||
try {
|
||||
window.ym?.(METRIKA_ID, 'reachGoal', goal)
|
||||
} catch (error) {
|
||||
// Аналитика не имеет права ломать отправку формы.
|
||||
console.warn('Metrika reachGoal failed', error)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Клик по кнопке звонка */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@@ -152,5 +171,6 @@ export function reportCallClick(): void {
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer?: Record<string, unknown>[]
|
||||
ym?: (counterId: number, action: string, ...args: unknown[]) => void
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user