// 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 (
{/* HERO */}
{/* Grafico SVG di sfondo */} {/* Griglia */} {[80,160,240,320,400].map(y => ( ))} {[100,200,300,400,500].map(x => ( ))} {/* Moving average blu */} {/* Moving average arancio */} {/* Area sotto la curva blu */} {/* Candele */} {[ [40, 360, 320, 305, 375], [90, 335, 295, 285, 348], [140, 305, 268, 260, 318], [190, 268, 232, 225, 275], [240, 238, 198, 192, 245], [290, 208, 172, 165, 215], [340, 182, 148, 140, 190], [390, 160, 126, 120, 168], [440, 138, 106, 100, 145], [490, 116, 86, 80, 123], [540, 98, 72, 66, 105], ].map(([x, o, c, l, h], i) => { const bullish = c < o; const color = bullish ? "#22c55e" : "#ef4444"; const top = Math.min(o, c), bot = Math.max(o, c); return ( ); })} {/* Overlay gradiente per leggibilità */}
{/* Contenuto */}
BVF Analyzer

BVF
Stock Analyzer

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

{/* FORM */}
{stage === "login" ? (

Accesso

Autenticazione a due fattori via Telegram.

{error && (
{error}
)}
setUsername(e.target.value)} placeholder="Username"/>
setPassword(e.target.value)} placeholder="••••••••"/>
Sistema interno · accesso riservato
) : (

Codice di verifica

Inviato sul tuo Telegram. Scade fra 9:42.

{error && (
{error}
)}
setOtp(e.target.value.replace(/\D/g, ""))} autoFocus placeholder="• • • • • •"/>
Non hai ricevuto il codice?{" "} {e.preventDefault();}}>Reinvia
)}
); } window.LoginPage = LoginPage;