feat: Телефон перенесен на шаг 1 (SMS верификация)\n\n- Новый шаг Step1Phone.tsx (отправка/проверка SMS)\n- ClaimForm: новая последовательность шагов (Телефон -> Полис -> Тип -> Документы -> Оплата)\n- Step3Payment: убран блок верификации телефона
This commit is contained in:
172
frontend/src/components/form/Step1Phone.tsx
Normal file
172
frontend/src/components/form/Step1Phone.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user