import { useState } from 'react'; import { Form, Input, Button, message, Space, Modal } from 'antd'; import { PhoneOutlined, SafetyOutlined, CopyOutlined } from '@ant-design/icons'; interface Props { formData: any; updateFormData: (data: any) => void; onNext: (unified_id?: string) => void; // ✅ Может принимать unified_id setIsPhoneVerified: (verified: boolean) => void; addDebugEvent?: (type: string, status: string, message: string, data?: any) => void; } export default function Step1Phone({ formData, updateFormData, onNext, setIsPhoneVerified, addDebugEvent }: Props) { // 🆕 VERSION CHECK: 2025-11-20 12:40 - session_id fix console.log('📱 Step1Phone v2.0 - 2025-11-20 14:40 - Session creation with debug logs'); const [form] = Form.useForm(); const [codeSent, setCodeSent] = useState(false); const [loading, setLoading] = useState(false); const [verifyLoading, setVerifyLoading] = useState(false); const [debugCode, setDebugCode] = useState(null); const [showDebugModal, setShowDebugModal] = useState(false); const sendCode = async () => { try { const values = await form.validateFields(['phone']); const phone = `7${values.phone}`; // БЕЗ +, формат: 79001234567 setLoading(true); addDebugEvent?.('sms', 'pending', `📱 Отправляю SMS на ${phone}...`, { phone }); const response = await fetch('/api/v1/sms/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone }) }); const result = await response.json(); if (response.ok) { addDebugEvent?.('sms', 'success', `✅ SMS отправлен (DEBUG mode)`, { phone, debug_code: result.debug_code, message: result.message }); message.success('Код отправлен на ваш телефон'); setCodeSent(true); updateFormData({ phone }); // 🔧 DEV MODE: показываем debug код в модалке if (result.debug_code) { setDebugCode(result.debug_code); setShowDebugModal(true); } } else { addDebugEvent?.('sms', 'error', `❌ Ошибка SMS: ${result.detail}`, { error: result.detail }); message.error(result.detail || 'Ошибка отправки кода'); } } catch (error) { if ((error as any)?.errorFields) { message.error('Введите номер телефона'); } else { message.error('Ошибка соединения с сервером'); } } finally { setLoading(false); } }; const verifyCode = async () => { try { const values = await form.validateFields(['phone', 'smsCode']); const phone = `7${values.phone}`; // БЕЗ +, формат: 79001234567 const code = values.smsCode; setVerifyLoading(true); addDebugEvent?.('sms', 'pending', `🔐 Проверяю SMS код...`, { phone, code }); const response = await fetch('/api/v1/sms/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone, code }) }); const result = await response.json(); if (response.ok) { addDebugEvent?.('sms', 'success', `✅ Телефон подтвержден успешно`, { phone, verified: true }); message.success('Телефон подтвержден!'); setIsPhoneVerified(true); // После верификации создаём контакт в CRM через n8n try { addDebugEvent?.('crm', 'info', '📞 Создание контакта в CRM...', { phone }); const crmResponse = await fetch('/api/n8n/contact/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone, session_id: formData.session_id, // ✅ Передаём session_id form_id: 'ticket_form' // ✅ Маркируем источник формы }) }); let crmResult = await crmResponse.json(); // ✅ n8n может вернуть массив - берём первый элемент if (Array.isArray(crmResult) && crmResult.length > 0) { crmResult = crmResult[0]; } console.log('🔥 N8N CRM Response (after array check):', crmResult); console.log('🔥 N8N CRM Response FULL:', JSON.stringify(crmResult, null, 2)); if (crmResponse.ok && crmResult.success) { // n8n возвращает: {success: true, result: {claim_id, contact_id, ...}} const result = crmResult.result || crmResult; console.log('🔥 Extracted result:', result); console.log('🔥 result.unified_id:', result.unified_id); console.log('🔥 typeof result.unified_id:', typeof result.unified_id); console.log('🔥 result keys:', Object.keys(result)); // ✅ ВАЖНО: Проверяем наличие unified_id if (!result.unified_id) { console.error('❌ unified_id отсутствует в ответе n8n!'); console.error('❌ Полный ответ result:', result); console.error('❌ Полный ответ crmResult:', crmResult); message.warning('⚠️ unified_id не получен от n8n, черновики могут не отображаться'); } else { console.log('✅ unified_id получен:', result.unified_id); } // ✅ Извлекаем session_id от n8n (если есть) const session_id_from_n8n = result.session; console.log('🔍 Проверка session_id от n8n:'); console.log('🔍 result.session:', result.session); console.log('🔍 session_id_from_n8n:', session_id_from_n8n); console.log('🔍 formData.session_id (текущий):', formData.session_id); if (session_id_from_n8n) { console.log('✅ session_id получен от n8n:', session_id_from_n8n); } else { console.warn('⚠️ session_id не найден в ответе n8n, используем текущий:', formData.session_id); } const finalSessionId = session_id_from_n8n || formData.session_id; console.log('🔍 finalSessionId (будет сохранён):', finalSessionId); const dataToSave = { phone, smsCode: code, contact_id: result.contact_id, unified_id: result.unified_id, // ✅ Unified ID из PostgreSQL (получаем от n8n) session_id: finalSessionId, // ✅ Используем session_id от n8n, если есть // claim_id убран - используем только session_id на этих этапах is_new_contact: result.is_new_contact }; console.log('🔥 ========== SAVING TO FORMDATA =========='); console.log('🔥 Saving to formData:', JSON.stringify(dataToSave, null, 2)); console.log('🔥 dataToSave.unified_id:', dataToSave.unified_id); console.log('🔥 dataToSave.session_id:', dataToSave.session_id); console.log('🔥 ========================================='); addDebugEvent?.('crm', 'success', `✅ Контакт создан/найден в CRM`, result); // Сохраняем данные из CRM в форму updateFormData(dataToSave); message.success(result.is_new_contact ? 'Контакт создан!' : 'Контакт найден!'); // ✅ Устанавливаем isPhoneVerified = true после успешной верификации setIsPhoneVerified(true); // 🔑 Создаём сессию в Redis для живучести (24 часа) try { console.log('🔑 Создаём сессию в Redis:', { session_token: finalSessionId, unified_id: result.unified_id, phone: phone, contact_id: result.contact_id }); const sessionResponse = await fetch('/api/v1/session/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_token: finalSessionId, unified_id: result.unified_id, phone: phone, contact_id: result.contact_id, ttl_hours: 24 }) }); console.log('🔑 Session create response status:', sessionResponse.status); if (sessionResponse.ok) { const sessionData = await sessionResponse.json(); console.log('🔑 Session create response data:', sessionData); // Сохраняем session_token в localStorage для последующих визитов localStorage.setItem('session_token', finalSessionId); console.log('✅ Сессия создана в Redis, session_token сохранён в localStorage:', finalSessionId); console.log('✅ Проверка: localStorage.getItem("session_token"):', localStorage.getItem('session_token')); addDebugEvent?.('session', 'success', '✅ Сессия создана (TTL 24h)'); } else { const errorText = await sessionResponse.text(); console.warn('⚠️ Не удалось создать сессию в Redis:', sessionResponse.status, errorText); } } catch (sessionError) { console.error('❌ Ошибка создания сессии:', sessionError); // Не блокируем дальнейшую работу } // ✅ Передаем unified_id напрямую в onNext для проверки черновиков // Это нужно, потому что formData может еще не обновиться const unifiedIdToPass = result.unified_id; console.log('🔥 ============================================'); console.log('🔥 Передаём unified_id в onNext:', unifiedIdToPass); console.log('🔥 typeof unifiedIdToPass:', typeof unifiedIdToPass); console.log('🔥 Вызываем onNext с unified_id:', unifiedIdToPass); console.log('🔥 ============================================'); onNext(unifiedIdToPass); } else { addDebugEvent?.('crm', 'error', '❌ Ошибка создания контакта в CRM', crmResult); message.error('Ошибка создания контакта в CRM'); } } catch (crmError) { addDebugEvent?.('crm', 'error', '❌ Ошибка соединения с CRM', { error: String(crmError) }); message.error('Ошибка соединения с CRM'); } } else { addDebugEvent?.('sms', 'error', `❌ Неверный код SMS`, { phone, code, error: result.detail }); message.error(result.detail || 'Неверный код'); } } catch (error) { if ((error as any)?.errorFields) { message.error('Введите код из SMS'); } else { message.error('Ошибка соединения с сервером'); } } finally { setVerifyLoading(false); } }; return (

📱 Подтверждение телефона

} placeholder="9001234567" maxLength={10} size="large" style={{ flex: 1 }} onPaste={(e) => { // Обработка вставки: очищаем от +7, пробелов и других символов e.preventDefault(); const pastedText = (e.clipboardData || (window as any).clipboardData).getData('text'); // Убираем все нецифровые символы let cleanText = pastedText.replace(/\D/g, ''); // Если начинается с 7 или 8, убираем первую цифру (код страны) if (cleanText.length === 11 && (cleanText.startsWith('7') || cleanText.startsWith('8'))) { cleanText = cleanText.substring(1); } // Оставляем только первые 10 цифр cleanText = cleanText.substring(0, 10); // ✅ Устанавливаем значение напрямую в input, затем синхронизируем с формой const target = e.target as HTMLInputElement; if (target) { target.value = cleanText; // Триггерим событие input для синхронизации с формой const inputEvent = new Event('input', { bubbles: true }); target.dispatchEvent(inputEvent); } // ✅ Синхронизируем с формой через requestAnimationFrame для избежания циклических ссылок requestAnimationFrame(() => { form.setFieldValue('phone', cleanText); // Показываем предупреждение, если номер был обрезан if (pastedText.replace(/\D/g, '').length > 10) { message.warning('Номер автоматически обрезан до 10 цифр'); } }); }} /> {!codeSent ? ( ) : ( } placeholder="123456" maxLength={6} style={{ width: '70%' }} size="large" name="smsCode" onChange={(e) => form.setFieldValue('smsCode', e.target.value)} /> )} {/* 🔧 DEV MODE: Модалка с SMS кодом */} setShowDebugModal(false)} footer={[ , ]} >

Это DEV режим. SMS не отправляется реально.

{debugCode}
); }