Port the single-file landing (2.6 MB of inlined CSS, JS and base64 images, kept as fitnes/legacy/index.html) to Vite + React 19 + TypeScript, with an Express API that files every form submission into amoCRM pipeline 10980758. - Extract the 14 embedded images to src/assets/images and public/ - Rebuild the design system as Tailwind v4 @theme tokens; the stock palette and breakpoints are cleared so only the EXO scale is reachable from utilities - Split the page into 15 components; all copy moves to src/data - Lead endpoint: find-or-create the contact (Russian phone spellings compared on the last 10 digits), create the lead in the pipeline's first stage, map the fields the account already has and put the rest in a note. If amoCRM is unreachable the payload is logged and kept in localStorage rather than lost. - Add a callback modal as a second entry point, tagged separately in the pipeline - Self-host Inter Variable so the layout's 760/850/900 weights render as real weights instead of snapping to bold Fidelity was checked by comparing section offsets and heights against the original at 375/480/640/900/1120/1440 px; every section and the total page height matched exactly. Loading Inter deliberately changes text metrics, so the byte-exact comparison holds against the pre-font build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
/**
|
|
* 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',
|
|
'city',
|
|
'club_format',
|
|
'configuration',
|
|
'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.')
|