feat: Телефон перенесен на шаг 1 (SMS верификация)\n\n- Новый шаг Step1Phone.tsx (отправка/проверка SMS)\n- ClaimForm: новая последовательность шагов (Телефон -> Полис -> Тип -> Документы -> Оплата)\n- Step3Payment: убран блок верификации телефона

This commit is contained in:
AI Assistant
2025-10-30 09:50:26 +03:00
parent 847dbe73a9
commit 58a12a3c05
3 changed files with 209 additions and 193 deletions

View File

@@ -0,0 +1,172 @@
import { useState } from 'react';
import { Form, Input, Button, message, Space } from 'antd';
import { PhoneOutlined, SafetyOutlined, MailOutlined } from '@ant-design/icons';
interface Props {
formData: any;
updateFormData: (data: any) => void;
onNext: () => void;
setIsPhoneVerified: (verified: boolean) => void;
addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
}
export default function Step1Phone({
formData,
updateFormData,
onNext,
setIsPhoneVerified,
addDebugEvent
}: Props) {
const [form] = Form.useForm();
const [codeSent, setCodeSent] = useState(false);
const [loading, setLoading] = useState(false);
const [verifyLoading, setVerifyLoading] = useState(false);
const sendCode = async () => {
try {
const values = await form.validateFields(['phone', 'email']);
const phone = values.phone;
setLoading(true);
addDebugEvent?.('sms', 'pending', `📱 Отправляю SMS на ${phone}...`, { phone });
const response = await fetch('http://147.45.146.17:8100/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: values.phone, email: values.email });
if (result.debug_code) {
message.info(`DEBUG: Код ${result.debug_code}`);
}
} else {
addDebugEvent?.('sms', 'error', `❌ Ошибка SMS: ${result.detail}`, { error: result.detail });
message.error(result.detail || 'Ошибка отправки кода');
}
} catch (error) {
if ((error as any)?.errorFields) {
message.error('Введите телефон и email');
} else {
message.error('Ошибка соединения с сервером');
}
} finally {
setLoading(false);
}
};
const verifyCode = async () => {
try {
const values = await form.validateFields(['phone', 'smsCode']);
const phone = values.phone;
const code = values.smsCode;
setVerifyLoading(true);
addDebugEvent?.('sms', 'pending', `🔐 Проверяю SMS код...`, { phone, code });
const response = await fetch('http://147.45.146.17:8100/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);
onNext();
} 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 (
<Form
form={form}
layout="vertical"
initialValues={formData}
style={{ marginTop: 24 }}
>
<h3 style={{ marginTop: 0 }}>📱 Подтверждение телефона</h3>
<Form.Item
label="Номер телефона"
name="phone"
rules={[
{ required: true, message: 'Введите номер телефона' },
{ pattern: /^\+7\d{10}$/, message: 'Формат: +79001234567' }
]}
>
<Input
prefix={<PhoneOutlined />}
placeholder="+79001234567"
maxLength={12}
size="large"
/>
</Form.Item>
<Form.Item
label="Электронная почта"
name="email"
rules={[
{ required: true, message: 'Введите email' },
{ type: 'email', message: 'Неверный формат email' }
]}
>
<Input
prefix={<MailOutlined />}
placeholder="example@mail.ru"
size="large"
type="email"
/>
</Form.Item>
<Form.Item>
{!codeSent ? (
<Button type="primary" onClick={sendCode} loading={loading} block>
Отправить код
</Button>
) : (
<Space.Compact style={{ width: '100%' }}>
<Input
prefix={<SafetyOutlined />}
placeholder="123456"
maxLength={6}
style={{ width: '70%' }}
size="large"
name="smsCode"
onChange={(e) => form.setFieldValue('smsCode', e.target.value)}
/>
<Button type="primary" onClick={verifyCode} loading={verifyLoading} style={{ width: '30%' }} size="large">
Проверить
</Button>
</Space.Compact>
)}
</Form.Item>
</Form>
);
}

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { Form, Input, Button, Select, message, Space, Divider } from 'antd'; import { Form, Input, Button, Select, message, Divider } from 'antd';
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined } from '@ant-design/icons'; import { QrcodeOutlined } from '@ant-design/icons';
const { Option } = Select; const { Option } = Select;
@@ -24,96 +24,12 @@ export default function Step3Payment({
addDebugEvent addDebugEvent
}: Props) { }: Props) {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [codeSent, setCodeSent] = useState(false); const [codeSent] = useState(false);
const [loading, setLoading] = useState(false); const [loading] = useState(false);
const [verifyLoading, setVerifyLoading] = useState(false); const [verifyLoading] = useState(false);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const sendCode = async () => { // Верификация телефона перенесена на шаг 1
try {
const phone = form.getFieldValue('phone');
if (!phone) {
message.error('Введите номер телефона');
return;
}
setLoading(true);
addDebugEvent?.('sms', 'pending', `📱 Отправляю SMS на ${phone}...`, { phone });
const response = await fetch('http://147.45.146.17:8100/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);
if (result.debug_code) {
message.info(`DEBUG: Код ${result.debug_code}`);
}
} else {
addDebugEvent?.('sms', 'error', `❌ Ошибка SMS: ${result.detail}`, { error: result.detail });
message.error(result.detail || 'Ошибка отправки кода');
}
} catch (error) {
message.error('Ошибка соединения с сервером');
} finally {
setLoading(false);
}
};
const verifyCode = async () => {
try {
const phone = form.getFieldValue('phone');
const code = form.getFieldValue('smsCode');
if (!code) {
message.error('Введите код из SMS');
return;
}
setVerifyLoading(true);
addDebugEvent?.('sms', 'pending', `🔐 Проверяю SMS код...`, { phone, code });
const response = await fetch('http://147.45.146.17:8100/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);
} else {
addDebugEvent?.('sms', 'error', `❌ Неверный код SMS`, {
phone,
code,
error: result.detail
});
message.error(result.detail || 'Неверный код');
}
} catch (error) {
message.error('Ошибка соединения с сервером');
} finally {
setVerifyLoading(false);
}
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
@@ -143,106 +59,18 @@ export default function Step3Payment({
</Button> </Button>
</div> </div>
{/* Блок верификации телефона */} {/* Блок верификации телефона перенесен на шаг 1 */}
<div style={{
padding: 16,
background: '#f6f8fa',
borderRadius: 8,
marginBottom: 24
}}>
<h3 style={{ marginTop: 0 }}>📱 Подтверждение телефона</h3>
<Form.Item
label="Номер телефона"
name="phone"
rules={[
{ required: true, message: 'Введите номер телефона' },
{ pattern: /^\+7\d{10}$/, message: 'Формат: +79001234567' }
]}
>
<Input
prefix={<PhoneOutlined />}
placeholder="+79001234567"
disabled={isPhoneVerified}
maxLength={12}
size="large"
/>
</Form.Item>
<Form.Item
label="Электронная почта"
name="email"
rules={[
{ required: true, message: 'Введите email' },
{ type: 'email', message: 'Неверный формат email' }
]}
>
<Input
prefix={<MailOutlined />}
placeholder="example@mail.ru"
size="large"
type="email"
disabled={isPhoneVerified}
/>
</Form.Item>
{!isPhoneVerified && (
<>
<Form.Item>
<Button
type="primary"
onClick={sendCode}
loading={loading}
disabled={codeSent}
block
>
{codeSent ? 'Код отправлен' : 'Отправить код'}
</Button>
</Form.Item>
{codeSent && (
<Form.Item
label="Код из SMS"
name="smsCode"
rules={[
{ required: true, message: 'Введите код' },
{ len: 6, message: '6 цифр' }
]}
>
<Space.Compact style={{ width: '100%' }}>
<Input
prefix={<SafetyOutlined />}
placeholder="123456"
maxLength={6}
style={{ width: '70%' }}
size="large"
/>
<Button
type="primary"
onClick={verifyCode}
loading={verifyLoading}
style={{ width: '30%' }}
size="large"
>
Проверить
</Button>
</Space.Compact>
</Form.Item>
)}
</>
)}
{isPhoneVerified && ( {isPhoneVerified && (
<div style={{ <div style={{
padding: 12, padding: 12,
background: '#f0f9ff', background: '#f0f9ff',
borderRadius: 8, borderRadius: 8,
border: '1px solid #91d5ff' border: '1px solid #91d5ff',
marginBottom: 24
}}> }}>
Телефон подтвержден Телефон подтвержден
</div> </div>
)} )}
</div>
{/* Блок выплаты (показывается только после верификации) */} {/* Блок выплаты (показывается только после верификации) */}
{isPhoneVerified && ( {isPhoneVerified && (

View File

@@ -1,6 +1,7 @@
import { useState, useMemo, useCallback } from 'react'; import { useState, useMemo, useCallback } from 'react';
import { Steps, Card, message, Row, Col } from 'antd'; import { Steps, Card, message, Row, Col } from 'antd';
import Step1Policy from '../components/form/Step1Policy'; import Step1Policy from '../components/form/Step1Policy';
import Step1Phone from '../components/form/Step1Phone';
import Step2EventType from '../components/form/Step2EventType'; import Step2EventType from '../components/form/Step2EventType';
import StepDocumentUpload from '../components/form/StepDocumentUpload'; import StepDocumentUpload from '../components/form/StepDocumentUpload';
import Step3Payment from '../components/form/Step3Payment'; import Step3Payment from '../components/form/Step3Payment';
@@ -165,7 +166,22 @@ export default function ClaimForm() {
const steps = useMemo(() => { const steps = useMemo(() => {
const stepsArray: any[] = []; const stepsArray: any[] = [];
// Шаг 1: Policy (всегда) // Шаг 1: Подтверждение телефона (всегда)
stepsArray.push({
title: 'Телефон',
description: 'Подтверждение по SMS',
content: (
<Step1Phone
formData={formData}
updateFormData={updateFormData}
onNext={nextStep}
setIsPhoneVerified={setIsPhoneVerified}
addDebugEvent={addDebugEvent}
/>
),
});
// Шаг 2: Policy (всегда)
stepsArray.push({ stepsArray.push({
title: 'Проверка полиса', title: 'Проверка полиса',
description: 'Полис ERV', description: 'Полис ERV',
@@ -179,7 +195,7 @@ export default function ClaimForm() {
), ),
}); });
// Шаг 2: Event Type Selection (всегда) // Шаг 3: Event Type Selection (всегда)
stepsArray.push({ stepsArray.push({
title: 'Тип события', title: 'Тип события',
description: 'Выбор случая', description: 'Выбор случая',
@@ -193,7 +209,7 @@ export default function ClaimForm() {
), ),
}); });
// Шаги 3+: Document Upload (динамически, если выбран eventType) // Шаги 4+: Document Upload (динамически, если выбран eventType)
if (formData.eventType && documentConfigs.length > 0) { if (formData.eventType && documentConfigs.length > 0) {
documentConfigs.forEach((docConfig, index) => { documentConfigs.forEach((docConfig, index) => {
stepsArray.push({ stepsArray.push({