import { useState, useEffect, createContext, useContext } from 'react'; import axios from 'axios'; import { API_URL } from '../../config/api'; import apiClient from '../../utils/apiClient'; import { useTranslation } from '../../i18n'; import { getProfile, updateProfile, changePassword, uploadAvatar, deleteAvatar, getSessions, terminateSession, getLoginHistory, getSSHKeys, addSSHKey, deleteSSHKey, getAPIKeys, createAPIKey, deleteAPIKey, getNotificationSettings, updateNotificationSettings, exportUserData, type UserProfile, type Session, type LoginHistoryEntry, type SSHKey, type APIKey, type NotificationSettings } from '../../services/userService'; type TabType = 'profile' | 'security' | 'notifications' | 'api' | 'ssh' | 'delete'; // Context for sharing isEn across settings tabs const SettingsLangContext = createContext(false); const useSettingsLang = () => useContext(SettingsLangContext); const SettingsPage = () => { const [activeTab, setActiveTab] = useState('profile'); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const { locale } = useTranslation(); const isEn = locale === 'en'; useEffect(() => { loadProfile(); }, []); const loadProfile = async () => { try { setLoading(true); const data = await getProfile(); setProfile(data); } catch (error) { console.error('Ошибка загрузки профиля:', error); } finally { setLoading(false); } }; if (loading) { return (
); } const tabs = [ { id: 'profile' as TabType, label: isEn ? 'Profile' : 'Профиль' }, { id: 'security' as TabType, label: isEn ? 'Security' : 'Безопасность' }, { id: 'notifications' as TabType, label: isEn ? 'Notifications' : 'Уведомления' }, { id: 'api' as TabType, label: isEn ? 'API Keys' : 'API ключи' }, { id: 'ssh' as TabType, label: isEn ? 'SSH Keys' : 'SSH ключи' }, { id: 'delete' as TabType, label: isEn ? 'Delete' : 'Удаление' }, ]; return (
{/* Заголовок */}

{isEn ? 'Account Settings' : 'Настройки аккаунта'}

{isEn ? 'Manage profile, security and integrations' : 'Управление профилем, безопасностью и интеграциями'}

{/* Sidebar с вкладками */}
{/* Контент вкладки */}
{activeTab === 'profile' && } {activeTab === 'security' && } {activeTab === 'notifications' && } {activeTab === 'api' && } {activeTab === 'ssh' && } {activeTab === 'delete' && }
); }; // ============ ПРОФИЛЬ ============ const ProfileTab = ({ profile, onUpdate }: { profile: UserProfile | null; onUpdate: () => void }) => { const isEn = useSettingsLang(); const [username, setUsername] = useState(profile?.username || ''); const [email, setEmail] = useState(profile?.email || ''); const [phoneNumber, setPhoneNumber] = useState(profile?.profile?.phoneNumber || ''); const [timezone, setTimezone] = useState(profile?.profile?.timezone || 'Europe/Moscow'); const [language, setLanguage] = useState(profile?.profile?.language || 'ru'); const [saving, setSaving] = useState(false); const [avatarFile, setAvatarFile] = useState(null); const [avatarPreview, setAvatarPreview] = useState(null); const handleAvatarChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { setAvatarFile(file); const reader = new FileReader(); reader.onloadend = () => { setAvatarPreview(reader.result as string); }; reader.readAsDataURL(file); } }; const handleAvatarUpload = async () => { if (!avatarFile) return; try { setSaving(true); await uploadAvatar(avatarFile); alert(isEn ? 'Avatar uploaded!' : 'Аватар загружен!'); onUpdate(); setAvatarFile(null); setAvatarPreview(null); } catch (error) { console.error('Ошибка загрузки аватара:', error); alert(isEn ? 'Error uploading avatar' : 'Ошибка загрузки аватара'); } finally { setSaving(false); } }; const handleDeleteAvatar = async () => { if (!confirm(isEn ? 'Delete avatar?' : 'Удалить аватар?')) return; try { setSaving(true); await deleteAvatar(); alert(isEn ? 'Avatar deleted' : 'Аватар удалён'); onUpdate(); } catch (error) { console.error('Ошибка удаления аватара:', error); alert(isEn ? 'Error deleting avatar' : 'Ошибка удаления аватара'); } finally { setSaving(false); } }; const handleSave = async () => { try { setSaving(true); await updateProfile({ username, email, phoneNumber, timezone, language }); alert(isEn ? 'Profile updated!' : 'Профиль обновлён!'); onUpdate(); } catch (error) { console.error('Ошибка обновления профиля:', error); alert(isEn ? 'Error updating profile' : 'Ошибка обновления профиля'); } finally { setSaving(false); } }; return (

{isEn ? 'Profile' : 'Профиль'}

{isEn ? 'Update your profile information' : 'Обновите информацию о своём профиле'}

{/* Аватар */}

{isEn ? 'Avatar' : 'Аватар'}

{avatarPreview || profile?.profile?.avatarUrl ? ( Avatar ) : ( )}
{avatarFile && ( )} {profile?.profile?.avatarUrl && ( )}
{/* Основная информация */}

{isEn ? 'Basic Information' : 'Основная информация'}

setUsername(e.target.value)} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" />
setEmail(e.target.value)} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" />
setPhoneNumber(e.target.value)} placeholder="+7 (999) 999-99-99" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" />
); }; // ============ БЕЗОПАСНОСТЬ ============ const SecurityTab = () => { const isEn = useSettingsLang(); const [view, setView] = useState<'password' | 'sessions'>('password'); return (

{isEn ? 'Security' : 'Безопасность'}

{isEn ? 'Manage password and active sessions' : 'Управление паролем и активными сеансами'}

{/* Sub-tabs */}
{view === 'password' && } {view === 'sessions' && }
); }; const PasswordChange = () => { const isEn = useSettingsLang(); const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [loading, setLoading] = useState(false); const getPasswordStrength = (password: string) => { if (password.length === 0) return { strength: 0, label: '' }; if (password.length < 6) return { strength: 1, label: isEn ? 'Weak' : 'Слабый', color: 'bg-red-500' }; if (password.length < 10) return { strength: 2, label: isEn ? 'Medium' : 'Средний', color: 'bg-yellow-500' }; if (!/[A-Z]/.test(password) || !/[0-9]/.test(password)) return { strength: 2, label: isEn ? 'Medium' : 'Средний', color: 'bg-yellow-500' }; return { strength: 3, label: isEn ? 'Strong' : 'Сильный', color: 'bg-green-500' }; }; const strength = getPasswordStrength(newPassword); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (newPassword !== confirmPassword) { alert(isEn ? 'Passwords do not match' : 'Пароли не совпадают'); return; } try { setLoading(true); await changePassword({ currentPassword, newPassword }); alert(isEn ? 'Password changed successfully!' : 'Пароль успешно изменён!'); setCurrentPassword(''); setNewPassword(''); setConfirmPassword(''); } catch (error) { console.error('Ошибка смены пароля:', error); alert(isEn ? 'Password change error. Check current password.' : 'Ошибка смены пароля. Проверьте текущий пароль.'); } finally { setLoading(false); } }; return (
setCurrentPassword(e.target.value)} required className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" />
setNewPassword(e.target.value)} required minLength={6} className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" /> {newPassword && (
{[1, 2, 3].map((level) => (
))}

{isEn ? 'Password strength:' : 'Сила пароля:'} {strength.label}

)}
setConfirmPassword(e.target.value)} required className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" />
); }; const ActiveSessions = () => { const isEn = useSettingsLang(); const [sessions, setSessions] = useState([]); const [loginHistory, setLoginHistory] = useState([]); const [loading, setLoading] = useState(true); const [showHistory, setShowHistory] = useState(false); useEffect(() => { loadSessions(); loadHistory(); }, []); const loadSessions = async () => { try { setLoading(true); const data = await getSessions(); setSessions(data); } catch (error) { console.error('Ошибка загрузки сеансов:', error); } finally { setLoading(false); } }; const loadHistory = async () => { try { const data = await getLoginHistory(20); setLoginHistory(data); } catch (error) { console.error('Ошибка загрузки истории:', error); } }; const handleTerminate = async (id: number) => { if (!confirm(isEn ? 'Are you sure you want to terminate this session?' : 'Вы уверены, что хотите завершить эту сессию?')) return; try { await terminateSession(id); alert(isEn ? 'Session terminated' : 'Сеанс завершён'); loadSessions(); } catch (error) { console.error('Ошибка завершения сеанса:', error); alert(isEn ? 'Failed to terminate session' : 'Не удалось завершить сессию'); } }; const handleTerminateAllOthers = async () => { if (!confirm(isEn ? 'Are you sure you want to terminate all other sessions?' : 'Вы уверены, что хотите завершить все остальные сессии?')) return; try { // Используем API для завершения всех остальных сессий await apiClient.delete('/api/sessions/others/all'); alert(isEn ? 'All other sessions terminated' : 'Все остальные сессии завершены'); loadSessions(); } catch (error) { console.error('Ошибка завершения сессий:', error); alert(isEn ? 'Failed to terminate sessions' : 'Не удалось завершить сессии'); } }; const getDeviceIcon = (device: string) => { const deviceLower = (device || 'desktop').toLowerCase(); if (deviceLower.includes('mobile') || deviceLower.includes('phone')) return '📱'; if (deviceLower.includes('tablet')) return '📱'; return '💻'; }; const formatRelativeTime = (dateString: string) => { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return isEn ? 'just now' : 'только что'; if (diffMins < 60) return isEn ? `${diffMins} min ago` : `${diffMins} мин. назад`; if (diffHours < 24) return isEn ? `${diffHours}h ago` : `${diffHours} ч. назад`; if (diffDays < 7) return isEn ? `${diffDays}d ago` : `${diffDays} дн. назад`; return date.toLocaleDateString(isEn ? 'en-US' : 'ru-RU'); }; if (loading) { return
Загрузка...
; } // Определяем количество других сессий для кнопки const otherSessions = sessions.filter(s => !s.isCurrent && !s.device?.includes('Current')); return (
{/* Кнопка завершения всех остальных сессий */} {otherSessions.length > 0 && (
)} {/* Сессии в виде карточек */}
{sessions.length === 0 ? (

{isEn ? 'No active sessions' : 'Нет активных сеансов'}

) : ( sessions.map((session) => { const isCurrent = session.isCurrent || session.device?.includes('Current'); return (
{/* Бейдж текущей сессии */} {isCurrent && (
✓ {isEn ? 'Current Session' : 'Текущая сессия'}
)} {/* Информация об устройстве */}
{getDeviceIcon(session.device || 'desktop')}

{session.browser || (isEn ? 'Unknown browser' : 'Неизвестный браузер')} · {session.device || 'Desktop'}

🌐 {session.ipAddress || (isEn ? 'Unknown' : 'Неизвестно')}

{session.location && (

📍 {session.location}

)}

⏱️ {isEn ? 'Activity' : 'Активность'}: {formatRelativeTime(session.lastActivity)}

🔐 {isEn ? 'Login' : 'Вход'}: {new Date(session.createdAt || session.lastActivity).toLocaleString(isEn ? 'en-US' : 'ru-RU')}

{/* Кнопка завершения */} {!isCurrent && (
)}
); }) )}
{/* История входов */}
{showHistory && (
{loginHistory.map((entry) => ( ))}
{isEn ? 'Status' : 'Статус'} {isEn ? 'IP Address' : 'IP адрес'} {isEn ? 'Device' : 'Устройство'} {isEn ? 'Date and Time' : 'Дата и время'}
{entry.success ? (isEn ? '✓ Success' : '✓ Успешно') : (isEn ? '✗ Error' : '✗ Ошибка')} {entry.ipAddress} {entry.userAgent ? entry.userAgent.substring(0, 60) + '...' : entry.device || (isEn ? 'Unknown' : 'Неизвестно')} {new Date(entry.createdAt).toLocaleString(isEn ? 'en-US' : 'ru-RU')}
)}
{/* Советы по безопасности */}

💡 {isEn ? 'Security Tips' : 'Советы по безопасности'}

  • • {isEn ? 'Regularly check the list of active sessions' : 'Регулярно проверяйте список активных сессий'}
  • • {isEn ? 'Terminate sessions on devices you no longer use' : 'Завершайте сессии на устройствах, которыми больше не пользуетесь'}
  • • {isEn ? 'If you see suspicious activity, immediately terminate all sessions and change password' : 'Если вы видите подозрительную активность, немедленно завершите все сессии и смените пароль'}
  • • {isEn ? 'Use strong passwords and two-factor authentication' : 'Используйте надёжные пароли и двухфакторную аутентификацию'}
); }; // ============ УВЕДОМЛЕНИЯ ============ const NotificationsTab = () => { const isEn = useSettingsLang(); const [settings, setSettings] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); useEffect(() => { loadSettings(); }, []); const loadSettings = async () => { try { setLoading(true); const data = await getNotificationSettings(); setSettings(data); } catch (error) { console.error('Ошибка загрузки настроек:', error); } finally { setLoading(false); } }; const handleToggle = async (field: keyof NotificationSettings, value: boolean) => { if (!settings) return; const updated = { ...settings, [field]: value }; setSettings(updated); try { setSaving(true); await updateNotificationSettings({ [field]: value }); } catch (error) { console.error('Ошибка обновления настроек:', error); alert(isEn ? 'Error saving settings' : 'Ошибка сохранения настроек'); loadSettings(); // Revert } finally { setSaving(false); } }; if (loading) { return
Загрузка...
; } if (!settings) { return
{isEn ? 'Error loading settings' : 'Ошибка загрузки настроек'}
; } const emailSettings = [ { key: 'emailBalanceLow' as keyof NotificationSettings, label: 'Низкий баланс' }, { key: 'emailPaymentCharged' as keyof NotificationSettings, label: 'Списание оплаты' }, { key: 'emailTicketReply' as keyof NotificationSettings, label: 'Ответ на тикет' }, { key: 'emailNewsletter' as keyof NotificationSettings, label: 'Рассылка новостей' }, ]; const pushSettings = [ { key: 'pushBalanceLow' as keyof NotificationSettings, label: 'Низкий баланс' }, { key: 'pushPaymentCharged' as keyof NotificationSettings, label: 'Списание оплаты' }, { key: 'pushTicketReply' as keyof NotificationSettings, label: 'Ответ на тикет' }, ]; return (

{isEn ? 'Notifications' : 'Уведомления'}

{isEn ? 'Configure notification methods' : 'Настройте способы получения уведомлений'}

{/* Email уведомления */}

{isEn ? 'Email Notifications' : 'Email уведомления'}

{emailSettings.map((setting) => ( ))}
{/* Push уведомления */}

{isEn ? 'Push Notifications' : 'Push уведомления'}

{pushSettings.map((setting) => ( ))}
{saving && (
{isEn ? 'Saving...' : 'Сохранение...'}
)}
); }; // ============ API КЛЮЧИ ============ const APIKeysTab = () => { const isEn = useSettingsLang(); const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [newKey, setNewKey] = useState<{ fullKey: string } | null>(null); useEffect(() => { loadKeys(); }, []); const loadKeys = async () => { try { setLoading(true); const data = await getAPIKeys(); setKeys(data); } catch (error) { console.error('Ошибка загрузки ключей:', error); } finally { setLoading(false); } }; const handleCreate = async (name: string) => { try { const key = await createAPIKey({ name }); setNewKey(key); loadKeys(); } catch (error) { console.error('Ошибка создания ключа:', error); alert(isEn ? 'Error creating key' : 'Ошибка создания ключа'); } }; const handleDelete = async (id: number) => { if (!confirm(isEn ? 'Delete this API key? Applications using it will stop working.' : 'Удалить этот API ключ? Приложения, использующие его, перестанут работать.')) return; try { await deleteAPIKey(id); alert(isEn ? 'Key deleted' : 'Ключ удалён'); loadKeys(); } catch (error) { console.error('Ошибка удаления ключа:', error); alert(isEn ? 'Error deleting key' : 'Ошибка удаления ключа'); } }; if (loading) { return
Загрузка...
; } return (

{isEn ? 'API Keys' : 'API ключи'}

{isEn ? 'Manage integration keys' : 'Управление ключами для интеграций'}

{keys.length === 0 ? (

{isEn ? 'No keys created' : 'Нет созданных ключей'}

) : (
{keys.map((key) => ( ))}
{isEn ? 'Name' : 'Название'} {isEn ? 'Prefix' : 'Префикс'} {isEn ? 'Created' : 'Создан'} {isEn ? 'Last Used' : 'Последнее использование'} {isEn ? 'Actions' : 'Действия'}
{key.name} {key.prefix}... {new Date(key.createdAt).toLocaleDateString(isEn ? 'en-US' : 'ru-RU')} {key.lastUsed ? new Date(key.lastUsed).toLocaleDateString(isEn ? 'en-US' : 'ru-RU') : (isEn ? 'Never' : 'Никогда')}
)} {/* Модалка создания ключа */} {showModal && ( setShowModal(false)} onCreate={handleCreate} /> )} {/* Модалка показа нового ключа */} {newKey && ( setNewKey(null)} /> )}
); }; const CreateAPIKeyModal = ({ onClose, onCreate }: { onClose: () => void; onCreate: (name: string) => void }) => { const [name, setName] = useState(''); const [loading, setLoading] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); await onCreate(name); setLoading(false); onClose(); }; return (

Create API Key

setName(e.target.value)} required placeholder="My project" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" />
); }; const ShowNewKeyModal = ({ keyData, onClose }: { keyData: { fullKey: string }; onClose: () => void }) => { const [copied, setCopied] = useState(false); const handleCopy = () => { navigator.clipboard.writeText(keyData.fullKey); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (

Key created successfully!

Save this key now! It will not be shown again.

{keyData.fullKey}
); }; // ============ SSH КЛЮЧИ ============ const SSHKeysTab = () => { const isEn = useSettingsLang(); const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); useEffect(() => { loadKeys(); }, []); const loadKeys = async () => { try { setLoading(true); const data = await getSSHKeys(); setKeys(data); } catch (error) { console.error('Ошибка загрузки ключей:', error); } finally { setLoading(false); } }; const handleAdd = async (name: string, publicKey: string) => { try { await addSSHKey({ name, publicKey }); alert(isEn ? 'SSH key added' : 'SSH ключ добавлен'); loadKeys(); setShowModal(false); } catch (error) { console.error('Ошибка добавления ключа:', error); alert(isEn ? 'Error adding key. Check the format.' : 'Ошибка добавления ключа. Проверьте формат.'); } }; const handleDelete = async (id: number) => { if (!confirm(isEn ? 'Delete this SSH key?' : 'Удалить этот SSH ключ?')) return; try { await deleteSSHKey(id); alert(isEn ? 'Key deleted' : 'Ключ удалён'); loadKeys(); } catch (error) { console.error('Ошибка удаления ключа:', error); alert(isEn ? 'Error deleting key' : 'Ошибка удаления ключа'); } }; if (loading) { return
Загрузка...
; } return (

{isEn ? 'SSH Keys' : 'SSH ключи'}

{isEn ? 'Manage SSH keys for server access' : 'Управление SSH ключами для доступа к серверам'}

{keys.length === 0 ? (

{isEn ? 'No keys added' : 'Нет добавленных ключей'}

) : (
{keys.map((key) => (

{key.name}

{isEn ? 'Fingerprint' : 'Отпечаток'}: {key.fingerprint}

{isEn ? 'Added' : 'Добавлен'}: {new Date(key.createdAt).toLocaleDateString(isEn ? 'en-US' : 'ru-RU')} {key.lastUsed && ` • ${isEn ? 'Used' : 'Использован'}: ${new Date(key.lastUsed).toLocaleDateString(isEn ? 'en-US' : 'ru-RU')}`}

))}
)} {showModal && ( setShowModal(false)} onAdd={handleAdd} /> )}
); }; const AddSSHKeyModal = ({ onClose, onAdd }: { onClose: () => void; onAdd: (name: string, publicKey: string) => void }) => { const [name, setName] = useState(''); const [publicKey, setPublicKey] = useState(''); const [loading, setLoading] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); await onAdd(name, publicKey); setLoading(false); }; return (

Add SSH Key

setName(e.target.value)} required placeholder="My laptop" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-ospab-primary focus:border-transparent" />