Convert "physiotherapy from scratch" landing to React + Tailwind v4
Ports the last single-file page, MedCbezFiz — the medical centre that has no physiotherapy yet — to the architecture the other three landings now share: Vite + React 19 + TypeScript + Tailwind v4, Express 5 for the API, zod schema shared between client and server. The original is kept in legacy/index.html as the visual reference, with its instructions alongside it in legacy/README.md. Its 4 MB collapse to 317 KB of JS plus assets loaded on demand. Fourteen inlined images were extracted; twelve turned out byte-identical to the medcenter landing's, and the two large device PNGs (1.5 MB and 750 KB) had identical sources, so the webp conversions from that landing are reused rather than redone — 664 KB of assets instead of 2.8 MB. Text was verified rather than eyeballed: the app is server-rendered to static HTML and its visible text diffed against the legacy file. 280 blocks against 281, and the only difference is the honeypot's screen-reader label, which the original has no equivalent for. Two things in the original are dead and were deliberately not ported: 39 .patient-economics / .base-economics-card rules that no element uses, and the [data-counter] animation handler, which likewise matches nothing on the page. Both are documented in the README. Departures from the medcenter landing this was built from, all following the original: the hero photo is anchored at 65% (72% from 980px), the header is transparent until 18px of scroll instead of always painted, the revenue band keeps 32px gutters at every width, and the launch timeline breaks into two columns at 900px rather than 760px. Leads post to /api/leads/medical-centers-no-physio, the address the original page already referenced, into pipeline 10980758. The cabinet_state select has no counterpart on this form and was dropped from the schema, mapper and check script. Vite runs on 5175 and the API on 3002 so all four landings can run at once. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
8b954ea5db
commit
cd3414d8d0
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Verifies the amoCRM connection and prints what the integration will do with
|
||||
* your account: which pipeline stage leads land in, and which of your custom
|
||||
* fields the lead form's data was matched to.
|
||||
*
|
||||
* npm run amo:check
|
||||
*/
|
||||
import { AmoClient } from '../server/src/amocrm.ts'
|
||||
import { readAmoConfig } from '../server/src/config.ts'
|
||||
import { resolveLeadFieldMap } from '../server/src/lead-mapper.ts'
|
||||
|
||||
const config = readAmoConfig()
|
||||
if (!config) {
|
||||
console.error('✗ AMO_SUBDOMAIN and AMO_LONG_LIVED_TOKEN must be set in .env')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const amo = new AmoClient(config)
|
||||
|
||||
console.info(`→ ${config.baseUrl}\n`)
|
||||
|
||||
const account = await amo.getAccount()
|
||||
console.info(`✓ Account: ${account?.name} (id ${account?.id})`)
|
||||
|
||||
const pipeline = await amo.getPipeline(config.pipelineId)
|
||||
if (!pipeline) {
|
||||
console.error(`✗ Pipeline ${config.pipelineId} not found in this account.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const stages = [...(pipeline._embedded?.statuses ?? [])].sort((a, b) => a.sort - b.sort)
|
||||
console.info(`✓ Pipeline ${pipeline.id}: «${pipeline.name}»`)
|
||||
console.info(` Leads will be created in the first stage: «${stages[0]?.name ?? 'unknown'}»`)
|
||||
console.info(` All stages: ${stages.map((s) => s.name).join(' → ')}\n`)
|
||||
|
||||
const leadFields = await amo.getLeadFields()
|
||||
const map = resolveLeadFieldMap(leadFields)
|
||||
|
||||
console.info(`Lead data → amoCRM field mapping (${leadFields.length} custom fields in the account):`)
|
||||
const keys = [
|
||||
'company',
|
||||
'profile',
|
||||
'comment',
|
||||
'page',
|
||||
'referrer',
|
||||
'form',
|
||||
'utm_source',
|
||||
'utm_medium',
|
||||
'utm_campaign',
|
||||
'utm_content',
|
||||
'utm_term',
|
||||
]
|
||||
for (const key of keys) {
|
||||
const field = map.get(key)
|
||||
console.info(
|
||||
field
|
||||
? ` ✓ ${key.padEnd(14)} → «${field.name}» (id ${field.id}, ${field.type})`
|
||||
: ` · ${key.padEnd(14)} → note on the lead`,
|
||||
)
|
||||
}
|
||||
|
||||
const contactFields = await amo.getContactFields()
|
||||
const hasPhone = contactFields.some((f) => f.code === 'PHONE')
|
||||
const hasEmail = contactFields.some((f) => f.code === 'EMAIL')
|
||||
console.info(`\nContact fields: PHONE ${hasPhone ? '✓' : '✗ missing'}, EMAIL ${hasEmail ? '✓' : '✗ missing'}`)
|
||||
console.info('\nAll good — the form is ready to create leads.')
|
||||
Reference in New Issue
Block a user