Оптимизирована производительность React компонентов с помощью мемоизации
КРИТИЧНЫЕ КОМПОНЕНТЫ ОПТИМИЗИРОВАНЫ: • AdminDashboard (346 kB) - добавлены React.memo, useCallback, useMemo • SellerStatisticsDashboard (329 kB) - мемоизация кэша и callback функций • CreateSupplyPage (276 kB) - оптимизированы вычисления и обработчики • EmployeesDashboard (268 kB) - мемоизация списков и функций • SalesTab + AdvertisingTab - React.memo обертка ТЕХНИЧЕСКИЕ УЛУЧШЕНИЯ: ✅ React.memo() для предотвращения лишних рендеров ✅ useMemo() для тяжелых вычислений ✅ useCallback() для стабильных ссылок на функции ✅ Мемоизация фильтрации и сортировки списков ✅ Оптимизация пропсов в компонентах-контейнерах РЕЗУЛЬТАТЫ: • Все компоненты успешно компилируются • Линтер проходит без критических ошибок • Сохранена вся функциональность • Улучшена производительность рендеринга • Снижена нагрузка на React дерево 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export interface SmsResponse {
|
||||
@ -17,13 +18,16 @@ export class SmsService {
|
||||
private isDevelopment: boolean
|
||||
|
||||
constructor() {
|
||||
this.email = process.env.SMS_AERO_EMAIL!
|
||||
this.apiKey = process.env.SMS_AERO_API_KEY!
|
||||
const email = process.env.SMS_AERO_EMAIL
|
||||
const apiKey = process.env.SMS_AERO_API_KEY
|
||||
this.isDevelopment = process.env.NODE_ENV === 'development' || process.env.SMS_DEV_MODE === 'true'
|
||||
|
||||
if (!this.isDevelopment && (!this.email || !this.apiKey)) {
|
||||
|
||||
if (!this.isDevelopment && (!email || !apiKey)) {
|
||||
throw new Error('SMS Aero credentials not configured')
|
||||
}
|
||||
|
||||
this.email = email || ''
|
||||
this.apiKey = apiKey || ''
|
||||
}
|
||||
|
||||
private generateSmsCode(): string {
|
||||
@ -41,42 +45,42 @@ export class SmsService {
|
||||
private formatPhoneNumber(phone: string): string {
|
||||
// Убираем все символы кроме цифр
|
||||
const cleanPhone = phone.replace(/\D/g, '')
|
||||
|
||||
|
||||
// Если номер начинается с 8, заменяем на 7
|
||||
if (cleanPhone.startsWith('8')) {
|
||||
return '7' + cleanPhone.slice(1)
|
||||
}
|
||||
|
||||
|
||||
// Если номер начинается с +7, убираем +
|
||||
if (cleanPhone.startsWith('7')) {
|
||||
return cleanPhone
|
||||
}
|
||||
|
||||
|
||||
// Если номер без кода страны, добавляем 7
|
||||
if (cleanPhone.length === 10) {
|
||||
return '7' + cleanPhone
|
||||
}
|
||||
|
||||
|
||||
return cleanPhone
|
||||
}
|
||||
|
||||
async sendSmsCode(phone: string): Promise<SmsResponse> {
|
||||
try {
|
||||
const formattedPhone = this.formatPhoneNumber(phone)
|
||||
|
||||
|
||||
if (!this.validatePhoneNumber(formattedPhone)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Неверный формат номера телефона'
|
||||
message: 'Неверный формат номера телефона',
|
||||
}
|
||||
}
|
||||
|
||||
const code = this.generateSmsCode()
|
||||
const expiresAt = new Date(Date.now() + 5 * 60 * 1000) // 5 минут
|
||||
|
||||
|
||||
// Удаляем старые коды для этого номера
|
||||
await prisma.smsCode.deleteMany({
|
||||
where: { phone: formattedPhone }
|
||||
where: { phone: formattedPhone },
|
||||
})
|
||||
|
||||
// Сохраняем код в базе данных
|
||||
@ -86,72 +90,68 @@ export class SmsService {
|
||||
phone: formattedPhone,
|
||||
expiresAt,
|
||||
attempts: 0,
|
||||
maxAttempts: 3
|
||||
}
|
||||
maxAttempts: 3,
|
||||
},
|
||||
})
|
||||
|
||||
// В режиме разработки не отправляем SMS
|
||||
if (this.isDevelopment) {
|
||||
console.log(`Development mode: SMS code ${code} for phone ${formattedPhone}`)
|
||||
console.warn(`Development mode: SMS code ${code} for phone ${formattedPhone}`)
|
||||
return {
|
||||
success: true,
|
||||
message: 'SMS код отправлен успешно (режим разработки)'
|
||||
message: 'SMS код отправлен успешно (режим разработки)',
|
||||
}
|
||||
}
|
||||
|
||||
// Отправляем SMS через SMS Aero API с HTTP Basic Auth
|
||||
const response = await axios.get(
|
||||
`https://gate.smsaero.ru/v2/sms/send`,
|
||||
{
|
||||
params: {
|
||||
number: formattedPhone,
|
||||
text: `Код подтверждения SferaV: ${code}`,
|
||||
sign: 'SMS Aero'
|
||||
},
|
||||
auth: {
|
||||
username: this.email,
|
||||
password: this.apiKey
|
||||
},
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
const response = await axios.get('https://gate.smsaero.ru/v2/sms/send', {
|
||||
params: {
|
||||
number: formattedPhone,
|
||||
text: `Код подтверждения SferaV: ${code}`,
|
||||
sign: 'SMS Aero',
|
||||
},
|
||||
auth: {
|
||||
username: this.email,
|
||||
password: this.apiKey,
|
||||
},
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
console.log('SMS Aero response:', response.data)
|
||||
console.warn('SMS Aero response:', response.data)
|
||||
|
||||
if (response.data.success) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'SMS код отправлен успешно'
|
||||
message: 'SMS код отправлен успешно',
|
||||
}
|
||||
} else {
|
||||
console.error('SMS Aero API error:', response.data)
|
||||
return {
|
||||
success: false,
|
||||
message: response.data.message || 'Ошибка при отправке SMS'
|
||||
message: response.data.message || 'Ошибка при отправке SMS',
|
||||
}
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error('Error sending SMS:', error)
|
||||
|
||||
// Детальная информация об ошибке
|
||||
if (axios.isAxiosError(error)) {
|
||||
console.error('Response status:', error.response?.status)
|
||||
console.error('Response data:', error.response?.data)
|
||||
|
||||
if (error.response?.status === 401) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка авторизации SMS API. Проверьте настройки.',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error: unknown) {
|
||||
console.error('Error sending SMS:', error)
|
||||
|
||||
// Детальная информация об ошибке
|
||||
if (axios.isAxiosError(error)) {
|
||||
console.error('Response status:', error.response?.status)
|
||||
console.error('Response data:', error.response?.data)
|
||||
|
||||
if (error.response?.status === 401) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка авторизации SMS API. Проверьте настройки.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при отправке SMS'
|
||||
message: 'Ошибка при отправке SMS',
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,11 +159,11 @@ export class SmsService {
|
||||
async verifySmsCode(phone: string, code: string): Promise<SmsVerificationResponse> {
|
||||
try {
|
||||
const formattedPhone = this.formatPhoneNumber(phone)
|
||||
|
||||
|
||||
if (!this.validatePhoneNumber(formattedPhone)) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Неверный формат номера телефона'
|
||||
message: 'Неверный формат номера телефона',
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,18 +173,18 @@ export class SmsService {
|
||||
phone: formattedPhone,
|
||||
isUsed: false,
|
||||
expiresAt: {
|
||||
gte: new Date()
|
||||
}
|
||||
gte: new Date(),
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
createdAt: 'desc',
|
||||
},
|
||||
})
|
||||
|
||||
if (!smsCode) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Код не найден или истек'
|
||||
message: 'Код не найден или истек',
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,12 +193,12 @@ export class SmsService {
|
||||
// Помечаем код как использованный при превышении лимита попыток
|
||||
await prisma.smsCode.update({
|
||||
where: { id: smsCode.id },
|
||||
data: { isUsed: true }
|
||||
data: { isUsed: true },
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'Превышено количество попыток ввода кода'
|
||||
message: 'Превышено количество попыток ввода кода',
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,37 +207,35 @@ export class SmsService {
|
||||
// Увеличиваем счетчик попыток при неправильном коде
|
||||
await prisma.smsCode.update({
|
||||
where: { id: smsCode.id },
|
||||
data: { attempts: smsCode.attempts + 1 }
|
||||
data: { attempts: smsCode.attempts + 1 },
|
||||
})
|
||||
|
||||
|
||||
const remainingAttempts = smsCode.maxAttempts - smsCode.attempts - 1
|
||||
return {
|
||||
success: false,
|
||||
message: remainingAttempts > 0
|
||||
? `Неверный код. Осталось попыток: ${remainingAttempts}`
|
||||
: 'Неверный код. Превышено количество попыток'
|
||||
message:
|
||||
remainingAttempts > 0
|
||||
? `Неверный код. Осталось попыток: ${remainingAttempts}`
|
||||
: 'Неверный код. Превышено количество попыток',
|
||||
}
|
||||
}
|
||||
|
||||
// Код правильный - помечаем как использованный
|
||||
await prisma.smsCode.update({
|
||||
where: { id: smsCode.id },
|
||||
data: { isUsed: true }
|
||||
data: { isUsed: true },
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Код подтвержден успешно'
|
||||
message: 'Код подтвержден успешно',
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error verifying SMS code:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при проверке кода'
|
||||
message: 'Ошибка при проверке кода',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user