Convert medical-centre landing to React + Tailwind v4 with amoCRM lead capture
Ports the single-file MedCsFiz page — the medical centre that already runs physiotherapy and wants the existing room to earn more — to the same architecture as the fitness and hotel landings: Vite + React 19 + TypeScript + Tailwind v4 on the client, Express 5 for the API, zod schema shared between the two. The original stays in legacy/index.html as the visual reference. Its 4 MB of inlined CSS, JS and base64 images become 12 asset files (664 KB) plus a bundle loaded on demand; the two 1.5 MB / 750 KB device PNGs are recompressed to webp. The page is split into 18 components with all copy moved to src/data/content.ts. Leads reuse the fitness amoCRM integration: contact lookup by phone in every Russian spelling, deal in the first stage of pipeline 10980758, account fields matched automatically with the rest written to a note. Rate limit, honeypot and a localStorage fallback so a lead survives the CRM being down. Endpoint is /api/leads/medical-centers-existing-physio; Vite runs on 5173 and the API on 3000. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
35f713a267
commit
8b954ea5db
@@ -0,0 +1,149 @@
|
||||
# Усиление действующего кабинета физиотерапии — лендинг для медицинских центров
|
||||
|
||||
React-приложение лендинга MedCsFiz (медицинский центр, у которого физиотерапия
|
||||
уже есть). Каждая заявка создаёт сделку в amoCRM (воронка `10980758`) и контакт,
|
||||
если его ещё нет.
|
||||
|
||||
Стек: **Vite + React 19 + TypeScript + Tailwind CSS v4**, API — **Express 5**.
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
npm install
|
||||
cp .env.example .env # заполнить AMO_SUBDOMAIN и AMO_LONG_LIVED_TOKEN
|
||||
npm run dev
|
||||
```
|
||||
|
||||
`npm run dev` поднимает Vite на `http://localhost:5173` и API на `:3000`;
|
||||
запросы `/api/*` проксируются с фронтенда на API.
|
||||
|
||||
## Продакшен
|
||||
|
||||
```bash
|
||||
npm run build # сборка клиента в dist/client
|
||||
npm start # Express отдаёт dist/client и обрабатывает /api/*
|
||||
```
|
||||
|
||||
Один процесс Node на любом VPS. Порт — `PORT` (по умолчанию 3000).
|
||||
|
||||
---
|
||||
|
||||
## amoCRM
|
||||
|
||||
### 1. Получить долгосрочный токен
|
||||
|
||||
1. amoCRM → **Настройки → Интеграции → Создать интеграцию → Внешняя интеграция**.
|
||||
2. Название любое (например «Сайт medcenter»), redirect URI — любой рабочий
|
||||
адрес сайта: для долгосрочного токена он не используется.
|
||||
3. Права доступа: достаточно **Сделки, Контакты** (чтение и запись).
|
||||
4. Сохранить, открыть интеграцию → вкладка **«Ключи и скопы»** →
|
||||
**«Генерировать токен»** (долгосрочный, действует ~1 год).
|
||||
5. Скопировать токен в `.env` в `AMO_LONG_LIVED_TOKEN`, поддомен — в
|
||||
`AMO_SUBDOMAIN` (для `https://exotherapy.amocrm.ru` это `exotherapy`).
|
||||
|
||||
Токен читается только на сервере и никогда не попадает в браузерный бандл.
|
||||
Пометьте в календаре дату истечения — токен нужно перевыпустить через год.
|
||||
|
||||
### 2. Проверить подключение
|
||||
|
||||
```bash
|
||||
npm run amo:check
|
||||
```
|
||||
|
||||
Скрипт выведет название аккаунта, первый этап воронки `10980758`, в который
|
||||
попадут сделки, и то, в какие поля вашего аккаунта легли данные формы.
|
||||
|
||||
### 3. Что происходит при отправке формы
|
||||
|
||||
1. Поиск контакта по телефону (во всех написаниях: `+7…`, `8…`, только цифры)
|
||||
и по email. Если контакт найден — используется он, недостающий телефон или
|
||||
email дописывается в карточку.
|
||||
2. Если контакта нет — создаётся новый с именем, телефоном и email.
|
||||
3. Создаётся сделка в воронке `AMO_PIPELINE_ID`, в её **первом этапе**,
|
||||
с тегами `AMO_LEAD_TAGS` + тег `заявка с сайта`.
|
||||
4. Данные, для которых в аккаунте есть подходящее поле (компания, состояние
|
||||
кабинета, профиль, `utm_*` и т.д.), пишутся в поля; всё остальное — в
|
||||
примечание к сделке. Ничего настраивать в amoCRM заранее не нужно.
|
||||
|
||||
Если amoCRM недоступна, заявка **не теряется**: она пишется в лог сервера
|
||||
(`[lead] payload was: …`) и сохраняется в `localStorage` браузера, а посетитель
|
||||
видит телефон для связи.
|
||||
|
||||
### Переменные окружения
|
||||
|
||||
| Переменная | Обязательна | Описание |
|
||||
|---|---|---|
|
||||
| `AMO_SUBDOMAIN` | да | Поддомен аккаунта, например `exotherapy` |
|
||||
| `AMO_LONG_LIVED_TOKEN` | да | Долгосрочный токен интеграции |
|
||||
| `AMO_PIPELINE_ID` | нет | Воронка для сделок (по умолчанию `10980758`) |
|
||||
| `AMO_RESPONSIBLE_USER_ID` | нет | Ответственный за сделку пользователь |
|
||||
| `AMO_LEAD_TAGS` | нет | Теги через запятую для всех сделок |
|
||||
| `PORT` | нет | Порт API/продакшен-сервера (`3000`) |
|
||||
| `VITE_SITE_URL` | нет | Канонический адрес, подставляется в `canonical` и `og:` |
|
||||
| `VITE_BASE_PATH` | нет | Подпуть размещения, например `/medical-centers/with-physiotherapy/` (по умолчанию `/`) |
|
||||
|
||||
---
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
index.html точка входа Vite (meta, Open Graph, JSON-LD)
|
||||
src/
|
||||
components/ секции лендинга и UI-примитивы
|
||||
data/content.ts весь текстовый контент страницы
|
||||
hooks/useReveal.ts анимация появления секций при скролле
|
||||
lib/ отправка заявки, маска телефона
|
||||
assets/images/ 12 изображений, извлечённых из исходного HTML
|
||||
index.css дизайн-токены Tailwind v4 (@theme) и базовые стили
|
||||
shared/lead.ts схема заявки (zod), общая для клиента и сервера
|
||||
server/src/ Express API + клиент amoCRM
|
||||
scripts/amo-check.ts диагностика подключения к amoCRM
|
||||
legacy/index.html исходный однофайловый лендинг (визуальный эталон)
|
||||
```
|
||||
|
||||
### Дизайн-система
|
||||
|
||||
Цвета, радиусы, тени, брейкпоинты и вертикальный ритм заданы токенами в
|
||||
`@theme` внутри `src/index.css`. Палитра Tailwind по умолчанию отключена
|
||||
(`--color-*: initial`), поэтому в разметке доступны только цвета EXO.
|
||||
Брейкпоинты соответствуют исходной вёрстке: `sm` 600, `md` 760, `lg` 980, плюс
|
||||
`max-width`-варианты `stack` (1050), `narrow` (760), `compact` (560),
|
||||
`phone` (520), `tiny` (430). Они объявлены от широкого к узкому — Tailwind
|
||||
выводит варианты в порядке объявления, и более узкий должен идти позже, чтобы
|
||||
выигрывать.
|
||||
|
||||
Горизонтальные ряды карточек (рынок, обещания, оборудование, программы,
|
||||
форматы, план) используют класс `.scroll-cards`: на телефоне это лента со
|
||||
snap-скроллом, а на нужной ширине секция сама возвращает её в обычную сетку
|
||||
через `sm:grid-still` / `md:grid-still`.
|
||||
|
||||
### Шрифты
|
||||
|
||||
Inter подключён локально пакетом `@fontsource-variable/inter` — внешних запросов
|
||||
(Google Fonts и т.п.) страница не делает. Это вариативный шрифт с диапазоном
|
||||
100–900, поэтому нестандартные веса из макета (720, 760, 790, 850) отрисовываются
|
||||
по-настоящему, а не округляются до `bold`.
|
||||
|
||||
Символы `≈ − ×` не входят в подсеты Inter у Fontsource, поэтому для них всегда
|
||||
используется системный фолбэк. Это заметно только при очень крупном кегле.
|
||||
|
||||
`Manrope` и `Segoe UI` остались в стеке `--font-sans` как фолбэк, но отдельно не
|
||||
загружаются.
|
||||
|
||||
### Экономика кабинета
|
||||
|
||||
Все цифры блоков «Деньги уходят мимо Вас» и «Сценарии выручки» — статические
|
||||
данные из `src/data/content.ts`, как в исходном лендинге. Калькулятора на
|
||||
странице нет: расчёт делается на аудите.
|
||||
|
||||
---
|
||||
|
||||
## Известные особенности
|
||||
|
||||
* `og:image` отдаётся по абсолютному адресу `VITE_SITE_URL` + `/og-image.webp`;
|
||||
при смене домена обновите `VITE_SITE_URL` перед сборкой.
|
||||
* Две фотографии аппаратов (ЭкзоЛазер В и ЭкзоИмпульс) в исходном файле были
|
||||
PNG на 1,5 МБ и 750 КБ — здесь они пережаты в webp с сохранением
|
||||
прозрачности, как и остальные снимки оборудования.
|
||||
Reference in New Issue
Block a user