import React, { useState, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; import { Icon } from "@iconify/react"; import { useAppSelector, useAppDispatch } from "../store/hooks"; import { userApi } from "../api/userApi"; import { authApi } from "../api/authApi"; import { clearAuth } from "../store/slices/authSlice"; import { setUser, setAiSettings } from "../store/slices/profileSlice"; import { setAccentColor as setAccentColorAction } from "../store/slices/uiSlice"; import { setAccentColor } from "../utils/colorUtils"; import { useNotification } from "../hooks/useNotification"; import { Modal } from "../components/common/Modal"; import { ThemeToggle } from "../components/common/ThemeToggle"; import { dbManager } from "../utils/indexedDB"; import { TwoFactorSetup } from "../components/twoFactor/TwoFactorSetup"; const ProfilePage: React.FC = () => { const navigate = useNavigate(); const dispatch = useAppDispatch(); const { showNotification } = useNotification(); // @ts-expect-error - переменная может использоваться в будущем const _user = useAppSelector((state) => state.profile.user); const [username, setUsername] = useState(""); const [email, setEmail] = useState(""); const [avatarUrl, setAvatarUrl] = useState(null); const [hasAvatar, setHasAvatar] = useState(false); const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [deletePassword, setDeletePassword] = useState(""); const [isDeleting, setIsDeleting] = useState(false); // 2FA settings const [twoFactorEnabled, setTwoFactorEnabled] = useState(false); const [isLoading2FA, setIsLoading2FA] = useState(false); // Public profile settings const [isPublicProfile, setIsPublicProfile] = useState(false); const [publicProfileLink, setPublicProfileLink] = useState(""); const avatarInputRef = useRef(null); useEffect(() => { loadProfile(); load2FAStatus(); }, []); const load2FAStatus = async () => { setIsLoading2FA(true); try { const status = await userApi.get2FAStatus(); setTwoFactorEnabled(status.twoFactorEnabled); } catch (error) { console.error("Ошибка загрузки статуса 2FA:", error); } finally { setIsLoading2FA(false); } }; const handle2FAStatusChange = async () => { await load2FAStatus(); await loadProfile(); }; const loadProfile = async () => { try { const userData = await userApi.getProfile(); dispatch(setUser(userData)); setUsername(userData.username || ""); setEmail(userData.email || ""); // Устанавливаем цвет акцента из профиля пользователя const accent = userData.accent_color || "#007bff"; dispatch(setAccentColorAction(accent)); setAccentColor(accent); if (userData.avatar) { setAvatarUrl(userData.avatar); setHasAvatar(true); } else { setAvatarUrl(null); setHasAvatar(false); } // Устанавливаем состояние публичного профиля setIsPublicProfile(userData.is_public_profile === 1); if (userData.is_public_profile === 1) { const link = `${window.location.origin}/public/${userData.username}`; setPublicProfileLink(link); } else { setPublicProfileLink(""); } // Загружаем AI настройки try { const aiSettings = await userApi.getAiSettings(); dispatch(setAiSettings(aiSettings)); } catch (aiError) { console.error("Ошибка загрузки AI настроек:", aiError); } } catch (error) { console.error("Ошибка загрузки профиля:", error); showNotification("Ошибка загрузки данных профиля", "error"); } }; const handleAvatarUpload = async ( event: React.ChangeEvent ) => { const file = event.target.files?.[0]; if (!file) return; // Проверка размера файла (5MB) if (file.size > 5 * 1024 * 1024) { showNotification( "Файл слишком большой. Максимальный размер: 5 МБ", "error" ); return; } // Проверка типа файла const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif"]; if (!allowedTypes.includes(file.type)) { showNotification( "Недопустимый формат файла. Используйте JPG, PNG или GIF", "error" ); return; } try { const result = await userApi.uploadAvatar(file); setAvatarUrl(result.avatar + "?t=" + Date.now()); setHasAvatar(true); await loadProfile(); showNotification("Аватарка успешно загружена", "success"); } catch (error: any) { console.error("Ошибка загрузки аватарки:", error); showNotification( error.response?.data?.error || "Ошибка загрузки аватарки", "error" ); } // Сбрасываем input if (avatarInputRef.current) { avatarInputRef.current.value = ""; } }; const handleDeleteAvatar = async () => { try { await userApi.deleteAvatar(); setAvatarUrl(null); setHasAvatar(false); await loadProfile(); showNotification("Аватарка успешно удалена", "success"); } catch (error: any) { console.error("Ошибка удаления аватарки:", error); showNotification( error.response?.data?.error || "Ошибка удаления аватарки", "error" ); } }; const handleUpdateProfile = async () => { if (!username.trim()) { showNotification("Логин не может быть пустым", "error"); return; } if (username.length < 3) { showNotification("Логин должен быть не менее 3 символов", "error"); return; } if (email && !isValidEmail(email)) { showNotification("Некорректный email адрес", "error"); return; } try { await userApi.updateProfile({ username: username.trim(), email: email.trim() || undefined, }); await loadProfile(); showNotification("Профиль успешно обновлен", "success"); } catch (error: any) { console.error("Ошибка обновления профиля:", error); showNotification( error.response?.data?.error || "Ошибка обновления профиля", "error" ); } }; const handleChangePassword = async () => { if (!currentPassword) { showNotification("Введите текущий пароль", "error"); return; } if (!newPassword) { showNotification("Введите новый пароль", "error"); return; } if (newPassword.length < 6) { showNotification("Новый пароль должен быть не менее 6 символов", "error"); return; } if (newPassword !== confirmPassword) { showNotification("Новый пароль и подтверждение не совпадают", "error"); return; } try { await userApi.updateProfile({ currentPassword, newPassword, }); setCurrentPassword(""); setNewPassword(""); setConfirmPassword(""); showNotification("Пароль успешно изменен", "success"); } catch (error: any) { console.error("Ошибка изменения пароля:", error); showNotification( error.response?.data?.error || "Ошибка изменения пароля", "error" ); } }; const handleDeleteAccount = async () => { if (!deletePassword.trim()) { showNotification("Введите пароль", "warning"); return; } setIsDeleting(true); try { await userApi.deleteAccount(deletePassword); // Очищаем IndexedDB при удалении аккаунта await dbManager.clearAll(); showNotification("Аккаунт успешно удален", "success"); dispatch(clearAuth()); setTimeout(() => { navigate("/"); }, 2000); } catch (error: any) { console.error("Ошибка удаления аккаунта:", error); showNotification( error.response?.data?.error || "Ошибка удаления аккаунта", "error" ); setIsDeleting(false); } }; const isValidEmail = (email: string) => { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email); }; const handlePublicProfileToggle = async () => { const newValue = !isPublicProfile; try { await userApi.updateProfile({ is_public_profile: newValue, }); setIsPublicProfile(newValue); if (newValue) { const link = `${window.location.origin}/public/${username}`; setPublicProfileLink(link); showNotification("Публичный профиль включен", "success"); } else { setPublicProfileLink(""); showNotification("Публичный профиль отключен", "success"); } await loadProfile(); } catch (error: any) { console.error("Ошибка обновления публичного профиля:", error); showNotification( error.response?.data?.error || "Ошибка обновления публичного профиля", "error" ); } }; const copyPublicProfileLink = () => { if (publicProfileLink) { navigator.clipboard.writeText(publicProfileLink); showNotification("Ссылка скопирована в буфер обмена", "success"); } }; return (
Личный кабинет
{/* Секция аватарки */}
{hasAvatar && avatarUrl ? ( Аватар ) : (
)}
{hasAvatar && ( )}

Максимальный размер: 5 МБ. Форматы: JPG, PNG, GIF

{/* Секция данных профиля */}

Данные профиля

setUsername(e.target.value)} />
setEmail(e.target.value)} />

Изменить пароль

setCurrentPassword(e.target.value)} />
setNewPassword(e.target.value)} />
setConfirmPassword(e.target.value)} />

Публичный профиль

{isPublicProfile && publicProfileLink && (

Ссылка на ваш публичный профиль:

)}

Безопасность

{isLoading2FA ? (

Загрузка...

) : ( )}

Удаление аккаунта - это необратимое действие. Все ваши заметки, изображения и данные будут удалены навсегда.

{/* Модальное окно подтверждения удаления аккаунта */} { setIsDeleteModalOpen(false); setDeletePassword(""); }} onConfirm={handleDeleteAccount} title="Удаление аккаунта" message={ <>

⚠️ ВНИМАНИЕ: Это действие нельзя отменить!

Вы действительно хотите удалить свой аккаунт? Все ваши заметки, изображения, настройки и данные будут удалены навсегда.

setDeletePassword(e.target.value)} onKeyPress={(e) => { if (e.key === "Enter" && !isDeleting) { handleDeleteAccount(); } }} />
} confirmText={isDeleting ? "Удаление..." : "Удалить аккаунт"} cancelText="Отмена" confirmType="danger" />
); }; export default ProfilePage;