Commit Graph
10 Commits
Author SHA1 Message Date
yuriy.pandClaude Opus 5 b32e7da506 Add the Top.Mail.Ru counter to all four landings with a lead goal
The counter (id 3795059) was only in fitnes and only counted pageViews.
Carry the snippet over to hotel, medcenterstart and medcenterphysio — the
same counter serves all four, as the Metrika one already does — and fire
goal 'form' from submitLead(), the single place both the request form and
the callback dialog pass through.

The goal sits inside the existing `leadId !== 0` guard: that response is
the honeypot decoy, so a bot submission must not register a conversion,
in Top.Mail.Ru no more than in Metrika.

The noscript pixel goes in the body next to the Metrika one, per 9af36cc;
the fitnes one, added in the head, moves down to join it.

Note that fitnes/index.html also carries a Prettier pass over the whole
file — the reflowed Metrika block is formatting only, no behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 22:08:29 +06:00
Yuriy PanovandClaude Opus 5 fc6a6dde8b Refresh package-lock.json in all four landings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 00:13:53 +06:00
Yuriy PanovandClaude Opus 5 9af36cc374 Move the Yandex.Metrika noscript pixel from head to body
A <noscript> in <head> may only contain link, style and meta, so parse5
rejected the <div><img> pixel (disallowed-content-in-noscript-in-head).
Applied to hotel, fitnes, medcenterphysio and medcenterstart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:52:37 +06:00
Yuriy PanovandClaude Opus 5 23986ff5ad medcenterphysio: apply the v3 design and add the callback dialog
v3 differs from legacy/new only by the callback dialog; the new phone
number was already in the React landing.

The header «Звонок» button opens the «Перезвоним вам» dialog from the
fitness landing (name, phone, consent), ported the same way as in
medcenterstart: the phone field keeps this landing's +7 mask and refuses
fewer than 11 digits, as the mockup does. The server accepts form
'callback', names the deal «Обратный звонок — <имя>» and tags it
«обратный звонок». The phone link inside the dialog and the call button
in the mobile action bar still report a call click.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:46:16 +06:00
Yuriy PanovandClaude Opus 5 cbf253c573 medcenterphysio: add the v3 mockup as the design reference
legacy/v3 is legacy/new plus the «Перезвоним вам» callback dialog and the
new phone number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:46:16 +06:00
Yuriy PanovandClaude Opus 5 8af2e463b3 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>
2026-09-09 01:30:47 +06:00
Yuriy PanovandClaude Opus 5 e13854e918 leads: new phone number and an amoCRM lead on every call-button click
The published number becomes +7 927 789-60-71 across all four landings —
`contacts` in each `src/data/content.ts`, plus the fallback messages in the
lead endpoints and `src/lib/lead.ts` that spell it out when amoCRM is down.
The `legacy/` reference pages keep the old number: they are the visual
originals, not something that ships.

Tapping the phone button now also creates a lead. It cannot reuse the form
path: a click carries no name and no number, so `callClickSchema` in
`shared/lead.ts` validates the tracking data alone, `LeadService.submitCallClick`
creates a contactless lead tagged `клик по телефону`, and `AmoClient.createLead`
takes an optional `contactId` for it. The mapper's field/note split moved into
`collect()` so both lead kinds share it.

The browser fires this with `sendBeacon` (falling back to `fetch keepalive`),
because the same click hands the page to `tel:` and a plain fetch would be
cut off mid-flight. Duplicates are held down from both ends: one lead per
browser session on the client, four per IP per 30 minutes on the server.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 00:58:15 +06:00
Yuriy PanovandClaude Opus 5 e7385e8898 analytics: add the Yandex.Metrika counter to all four landings
Counter 112352796 goes into the <head> of every landing's index.html, ahead
of the Vite entry, so the hit fires before React mounts. The pages are plain
SPAs served off disk by nginx (Express only answers /api/*), so the static
head is the only place it needs to be.

One counter serves all four domains, which means reports mix them and every
domain has to be listed in the counter's own settings or its hits get
filtered — noted in deploy/README.md along with the conversion tracking that
is still missing: src/lib/lead.ts pushes a plain custom dataLayer event,
which Metrika's ecommerce: 'dataLayer' does not read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 00:24:33 +06:00
Yuriy PanovandClaude Opus 5 51f39c9b88 deploy: follow the landing rename and the exorecovery.ru domains
Bring deploy/ in line with the renamed app directories and with the
domains that are actually configured in the per-app .env files:

  fitnes           fitness.exorecovery.ru  3000  fitness-landing
  hotel            hotel.exorecovery.ru    3001  hotel-landing
  medcenterphysio  physio.exorecovery.ru   3002  physio-landing
  medcenterstart   start.exorecovery.ru    3003  start-landing

apps.conf, the README provisioning steps (directories, bare clones,
systemd units, the sudoers whitelist) and the exo-deploy usage line all
used the old medcenter / medcenterpersonal keys. The README table and
app.build.env.example still carried the *_DOMAIN placeholders.

Also refresh the tracked .env.example files: real VITE_SITE_URL values
instead of the old exodevices.ru sub-paths, and the amo lead tags that
match apps.conf.

Two lockfile fixes, both of which broke `npm ci` in exo-deploy:
- medcenterstart had no lockfile at all after package-lock.json and
  pnpm-lock.yaml were removed — restore package-lock.json and keep the
  pnpm one deleted, since the deploy path is npm;
- medcenterphysio/package-lock.json had lost "resolved" and "integrity"
  on 147 of its 236 entries — restore the complete file.

Verified: exo-render-nginx renders all four vhosts with no leftover
placeholders, and every package-lock.json agrees with its package.json.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 23:36:17 +06:00
Yuriy PanovandClaude Opus 5 0732aa2096 Rename medcenter landings and add the revenue calculator
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>
2026-09-06 23:19:46 +06:00