/* Login / Register — flujo de auth con email + password. * * Modos: * - "login": pide email + password, hace POST /auth/login. * - "register": pide email + password (+ nombre opcional), hace POST /auth/register * y, si 201, hace login automático y redirige a onboarding. * - "demo": no toca el backend, setea api_fit_demo=1 y entra a la app con MOCKs. * * Onboarding/instalación nueva: si el backend responde GET /auth/has_users { has_users: false } * el modo arranca en "register" para forzar la creación del primer usuario. */ const Login = ({ onAuthenticated, onRegisteredFirstTime }) => { const [mode, setMode] = React.useState("login"); // login | register const [email, setEmail] = React.useState(""); const [password, setPassword] = React.useState(""); const [name, setName] = React.useState(""); const [base, setBase] = React.useState(API.getBase()); const [showHelp, setShowHelp] = React.useState(false); const [status, setStatus] = React.useState({ kind: "idle" }); const [bootChecked, setBootChecked] = React.useState(false); // Boot: chequear si hay usuarios en el sistema. Si no, forzar registro. React.useEffect(() => { let alive = true; (async () => { try { const res = await API.authHasUsers(); if (!alive) return; if (res && res.has_users === false) { setMode("register"); } } catch (_) { // backend caído o endpoint no implementado todavía: dejamos modo login. } finally { if (alive) setBootChecked(true); } })(); return () => { alive = false; }; }, []); const validate = () => { if (!email.includes("@")) return "Ingresá un email válido."; if (mode === "register" && password.length < 8) return "La contraseña debe tener al menos 8 caracteres."; if (!password) return "Ingresá una contraseña."; return null; }; const submit = async () => { const err = validate(); if (err) { setStatus({ kind: "bad", msg: err }); return; } if (base) API.setBase(base); setStatus({ kind: "loading" }); try { if (mode === "register") { await API.authRegister({ email: email.trim(), password, name: name.trim() || undefined }); // Auto-login después de registrar. await API.authLogin({ email: email.trim(), password }); // Limpiar flag de demo si quedaba. API.setDemoMode(false); setStatus({ kind: "ok", msg: "Cuenta creada. Entrando…" }); // Primera ejecución: ir al onboarding. setTimeout(() => (onRegisteredFirstTime || onAuthenticated)(), 250); } else { await API.authLogin({ email: email.trim(), password }); API.setDemoMode(false); setStatus({ kind: "ok", msg: "Sesión iniciada." }); setTimeout(onAuthenticated, 200); } } catch (e) { let msg; if (e.network) { msg = `El servidor no responde (${API.getBase() || "mismo origen"}). ¿Está corriendo el backend?`; } else if (e.status === 401) { msg = "Email o contraseña incorrectos."; } else if (e.status === 409) { msg = "Ese email ya está registrado. Probá iniciar sesión."; } else if (e.status === 400) { const detail = e.detail?.detail || ""; if (/password/i.test(detail)) msg = "La contraseña debe tener al menos 8 caracteres."; else if (/email/i.test(detail)) msg = "Email inválido."; else msg = detail || e.message; } else if (e.status >= 500) { msg = "El servidor tuvo un error. Probá de nuevo en un momento."; } else { msg = e.message || "Algo no anduvo."; } setStatus({ kind: "bad", msg }); } }; const skipToDemo = () => { API.setDemoMode(true); API.clearToken(); API.clearUser(); onAuthenticated(); }; const onKeyDown = (e) => { if (e.key === "Enter") submit(); }; return (
tu salud, en tu casa

{mode === "register" ? "Creá tu cuenta." : "Iniciá sesión."}

{mode === "register" ? "Una sola cuenta por instalación de api_fit. Tus credenciales viven en tu base de datos local." : "Usá el email y la contraseña con los que te registraste en este servidor."}
setEmail(e.target.value)} onKeyDown={onKeyDown} placeholder="email@dominio.com" autoFocus autoComplete="email" className="af-login-input" /> setPassword(e.target.value)} onKeyDown={onKeyDown} placeholder={mode === "register" ? "contraseña (mínimo 8 caracteres)" : "contraseña"} autoComplete={mode === "register" ? "new-password" : "current-password"} className="af-login-input" /> {mode === "register" && ( setName(e.target.value)} onKeyDown={onKeyDown} placeholder="nombre (opcional)" autoComplete="name" className="af-login-input" /> )}
{showHelp && ( setBase(e.target.value)} placeholder="http://localhost:8000" className="af-login-input" style={{ marginBottom: 12 }} /> )} {status.kind === "bad" && (
Error: {status.msg}
)} {status.kind === "ok" && (
{status.msg || "Listo. Entrando…"}
)}
🔒 local · datos en tu máquina
); }; window.Login = Login;