// Login + OTP screens function LoginPage({ onLogin }) { const [stage, setStage] = useState("login"); // 'login' | 'otp' const [error, setError] = useState(null); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [showPwd, setShowPwd] = useState(false); const [otp, setOtp] = useState(""); const [submitting, setSubmitting] = useState(false); const submitLogin = async (e) => { e.preventDefault(); setError(null); if (!username || !password) { setError("Inserisci username e password."); return; } setSubmitting(true); try { const res = await fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), }); const data = await res.json(); if (!data.ok) { setError(data.error || "Credenziali non valide"); } else if (data.stage === "otp") { setStage("otp"); } else { onLogin(data.user); } } catch { setError("Errore di rete. Riprova."); } finally { setSubmitting(false); } }; const submitOtp = async (e) => { e.preventDefault(); if (otp.length !== 6) { setError("Il codice deve essere di 6 cifre."); return; } setSubmitting(true); try { const res = await fetch("/api/verify-otp", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ code: otp }), }); const data = await res.json(); if (!data.ok) { setError(data.error || "Codice non valido o scaduto"); } else { onLogin(data.user); } } catch { setError("Errore di rete. Riprova."); } finally { setSubmitting(false); } }; return (
Analisi multi-layer in tempo reale: AI Agent, modello tecnico-statistico e sentiment news su ogni ticker del watchlist.
v2.4 · Solo per uso interno · Accesso monitorato