import React, { useState, useEffect } from "react"; import { invoke } from "@tauri-apps/api/core"; import logoUrl from "./assets/logo.png"; import { MailIcon, LockIcon, EyeIcon, EyeOffIcon, ArrowRightLoginIcon, AlertCircleIcon, CheckCircleIcon, SpinnerIcon, ChevronLeftIcon, UserIcon, } from "./components/Icons"; import { AuthResponse, DbStatus, ResetRequestResponse, SafeDbInfo, UserDto } from "./types"; import { OtpInput } from "./components/OtpInput"; import { UpdaterBanner } from "./components/UpdaterBanner"; import "./App.css"; import Dashboard from "./pages/dashboard/Dashboard"; const isTauri = () => { return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; }; export default function App() { const [mode, setMode] = useState<"login" | "register" | "reset">("login"); const [username, setUsername] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [rememberMe, setRememberMe] = useState(true); // Password reset state const [resetStage, setResetStage] = useState<1 | 2 | 3>(1); const [resetEmail, setResetEmail] = useState(""); const [resetCode, setResetCode] = useState(""); const [countdown, setCountdown] = useState(30); const [newPassword, setNewPassword] = useState(""); const [confirmNewPassword, setConfirmNewPassword] = useState(""); const [showNewPassword, setShowNewPassword] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [currentUser, setCurrentUser] = useState(null); const [dbStatus, setDbStatus] = useState(null); const [dbInfo, setDbInfo] = useState(null); const [showForgotModal, setShowForgotModal] = useState(false); // Check saved session on mount useEffect(() => { const savedUser = localStorage.getItem("has_remembered_user"); if (savedUser) { try { const parsed = JSON.parse(savedUser); if (parsed?.email && parsed.email.toLowerCase() === "ne60201@gmail.com") { parsed.role = "admin"; } setCurrentUser(parsed); } catch { localStorage.removeItem("has_remembered_user"); } } const savedEmail = localStorage.getItem("has_remembered_email"); if (savedEmail) { setEmail(savedEmail); } // Check DB status on startup checkDatabase(); }, []); // Countdown timer for OTP re-send in resetStage 2 useEffect(() => { if (resetStage !== 2) return; setCountdown(30); const timer = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { clearInterval(timer); return 0; } return prev - 1; }); }, 1000); return () => clearInterval(timer); }, [resetStage]); const checkDatabase = async () => { if (!isTauri()) { setDbStatus({ connected: false, host: "localhost", dbname: "postgres", message: "Приложение запущено в браузере. Запустите через Tauri для подключения к Postgres.", }); return; } try { const info = await invoke("get_db_info"); setDbInfo(info); const status = await invoke("check_db"); setDbStatus(status); } catch (err: any) { setDbStatus({ connected: false, host: "unknown", dbname: "unknown", message: String(err?.message || err), }); } }; const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSuccess(null); if (!email.trim() || !password) { setError("Пожалуйста, заполните email и пароль"); return; } if (!isTauri()) { setError("Для реального подключения к PostgreSQL запустите десктоп-приложение ('npm run tauri dev')"); return; } setLoading(true); try { const res = await invoke("login", { email: email.trim(), password, }); if (res.success && res.user) { setCurrentUser(res.user); if (rememberMe) { localStorage.setItem("has_remembered_user", JSON.stringify(res.user)); localStorage.setItem("has_remembered_email", email.trim()); } else { localStorage.removeItem("has_remembered_user"); localStorage.removeItem("has_remembered_email"); } } else { setError(res.message || "Ошибка входа. Проверьте данные."); } } catch (err: any) { setError(`Ошибка: ${err?.message || err}`); } finally { setLoading(false); } }; const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSuccess(null); if (!email.trim() || !password) { setError("Пожалуйста, укажите email и пароль"); return; } if (!username.trim()) { setError("Пожалуйста, укажите имя пользователя"); return; } if (password !== confirmPassword) { setError("Пароли не совпадают"); return; } if (!isTauri()) { setError("Для регистрации пользователя в PostgreSQL запустите через Tauri ('npm run tauri dev')"); return; } setLoading(true); try { const res = await invoke("register", { email: email.trim(), password, username: username.trim(), }); if (res.success) { setSuccess("Аккаунт успешно создан! Теперь вы можете войти."); setMode("login"); } else { setError(res.message || "Ошибка при создании аккаунта."); } } catch (err: any) { setError(`Ошибка: ${err?.message || err}`); } finally { setLoading(false); } }; const handleRequestResetCode = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSuccess(null); if (!resetEmail.trim()) { setError("Пожалуйста, введите адрес электронной почты"); return; } if (!isTauri()) { setError("Для работы сброса пароля запустите десктоп-приложение ('npm run tauri dev')"); return; } setLoading(true); try { const res = await invoke("request_reset_code", { email: resetEmail.trim(), }); if (res.success) { setResetStage(2); setSuccess(res.message); } else { setError(res.message || "Ошибка отправки проверочного кода"); } } catch (err: any) { setError(`Ошибка: ${err?.message || err}`); } finally { setLoading(false); } }; const handleVerifyResetCode = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSuccess(null); if (!resetCode.trim()) { setError("Пожалуйста, введите проверочный код"); return; } if (!isTauri()) { setError("Для проверки кода запустите десктоп-приложение"); return; } setLoading(true); try { const res = await invoke("verify_reset_code", { email: resetEmail.trim(), code: resetCode.trim(), }); if (res.success) { setResetStage(3); setSuccess("Код подтвержден! Теперь задайте новый пароль."); } else { setError(res.message || "Неверный код подтверждения"); } } catch (err: any) { setError(`Ошибка: ${err?.message || err}`); } finally { setLoading(false); } }; const handleResetPassword = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSuccess(null); if (!newPassword) { setError("Пожалуйста, введите новый пароль"); return; } if (newPassword !== confirmNewPassword) { setError("Пароли не совпадают"); return; } if (!isTauri()) { setError("Для смены пароля запустите десктоп-приложение"); return; } setLoading(true); try { const res = await invoke("reset_password", { email: resetEmail.trim(), code: resetCode.trim(), newPassword, }); if (res.success) { setMode("login"); setEmail(resetEmail.trim()); setPassword(""); setResetEmail(""); setResetCode(""); setNewPassword(""); setConfirmNewPassword(""); setResetStage(1); setSuccess("Пароль успешно обновлен! Войдите с новым паролем."); } else { setError(res.message || "Не удалось изменить пароль"); } } catch (err: any) { setError(`Ошибка: ${err?.message || err}`); } finally { setLoading(false); } }; const handleLogout = () => { setCurrentUser(null); localStorage.removeItem("has_remembered_user"); setPassword(""); setConfirmPassword(""); setSuccess(null); setError(null); }; if (currentUser) { return ( <> ); } return (
{/* Auto-updater banner */} {/* Main Content Area */} {mode === "reset" ? ( /* Password Reset Flow */
{/* Project Branding */}
Home Autli Server Home Autli Server
{/* Title & Subtitle */}

{resetStage === 1 ? "Сброс пароля" : resetStage === 2 ? "Сброс пароля" : "Новый пароль"}

{resetStage === 1 ? "Забыли пароль? Введите email для получения проверочного кода." : resetStage === 2 ? "Введите 6-значный код подтверждения из письма." : "Код подтвержден. Введите новый пароль для вашей учетной записи:"}

{/* Notification Messages */} {error && (
{error}
)} {success && (
{success}
)} {/* Step 1: Request code by email */} {resetStage === 1 && (
setResetEmail(e.target.value)} required autoComplete="email" />
{ setMode("login"); setError(null); setSuccess(null); }} > Назад ко входу
)} {/* Step 2: Verify code (6 separate OTP boxes matching mockup) */} {resetStage === 2 && (
setResetCode(code)} disabled={loading} />
Не получили код?{" "} {countdown > 0 ? ( Отправить повторно через {countdown}с ) : ( { handleRequestResetCode(e); setCountdown(30); }} > Отправить код повторно )}
{ setMode("login"); setError(null); setSuccess(null); }} > Назад ко входу
)} {/* Step 3: Enter new password and confirmation */} {resetStage === 3 && (
setNewPassword(e.target.value)} required />
setConfirmNewPassword(e.target.value)} required />
{ setMode("login"); setError(null); setSuccess(null); }} > Назад ко входу
)}
) : ( /* Login / Register Card with actual logo & branding */
{/* Actual Project Logo and Name */}
Home Autli Server Home Autli Server
{/* Title & Subtitle */}

{mode === "login" ? "Вход в аккаунт" : "Создать аккаунт"}

{mode === "login" ? "Авторизация в системе Home Autli Server" : "Регистрация нового пользователя на сервере"}

{/* Notification Messages */} {error && (
{error}
)} {success && (
{success}
)} {/* Form */}
{/* Username (Register mode only) */} {mode === "register" && (
setUsername(e.target.value)} required autoComplete="name" />
)} {/* Email Address */}
setEmail(e.target.value)} required autoComplete="email" />
{/* Password */}
setPassword(e.target.value)} required autoComplete={mode === "login" ? "current-password" : "new-password"} />
{/* Confirm Password (Register mode) */} {mode === "register" && (
setConfirmPassword(e.target.value)} required />
)} {/* Remember Me & Forgot Password (Login mode) */} {mode === "login" && (
{ setMode("reset"); setResetStage(1); setResetEmail(email.trim()); setError(null); setSuccess(null); }} > Забыли пароль?
)} {/* Submit Button */} {/* Switch Mode Link */}
{mode === "login" ? ( <> Нет аккаунта?{" "} { setMode("register"); setUsername(""); setError(null); setSuccess(null); }} > Зарегистрироваться ) : ( <> Уже есть аккаунт?{" "} { setMode("login"); setUsername(""); setError(null); setSuccess(null); }} > Войти )}
)} {/* Footer */}
Copyright © 2026 Home Autli Server
{/*
Политика конфиденциальности Условия использования
*/}
{/* Forgot Password Modal */} {showForgotModal && (
setShowForgotModal(false)}>
e.stopPropagation()}>

Восстановление доступа

Для смены или сброса пароля обратитесь к системному администратору сервера либо воспользуйтесь функцией создания новой учетной записи.

)}
); }